From 4c8364cfe46ea57cbf2337f6257764ad4c8799b3 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 27 Oct 2020 14:58:52 -0400 Subject: [PATCH 001/276] Do not require pgBackRest Secret for cluster creation This changes the methodology for how a pgBackRest Secret is generated such that it is no longer required at the time a cluster is created vis-a-vis the pgcluster custom resource. Instead, the Operator follows this heuristic: - If a pgBackRest Secret is provided, this Secret is used - If the pgBackRest Secret is partially filled out, the missing pieces are filled in - If no Secret is provided, a Secret is generated. Note that if you want to use S3 or a S3-like storage system, you will need to still create the Secret with the appropriate S3 credentials. This also updates various documentation to show the easier workflow. Issue: [ch9451] --- docs/content/custom-resources/_index.md | 65 +-------- examples/create-by-resource/run.sh | 39 ----- examples/helm/README.md | 53 ++++--- .../apiserver/clusterservice/clusterimpl.go | 30 ++-- internal/operator/backrest/repo.go | 13 ++ internal/operator/cluster/cluster.go | 8 + internal/util/cluster.go | 137 ++++++++++++------ 7 files changed, 165 insertions(+), 180 deletions(-) diff --git a/docs/content/custom-resources/_index.md b/docs/content/custom-resources/_index.md index e27adfd3e6..af913755e9 100644 --- a/docs/content/custom-resources/_index.md +++ b/docs/content/custom-resources/_index.md @@ -255,68 +255,7 @@ create additional secrets. The following guide goes through how to create a PostgreSQL cluster called `hippo` by creating a new custom resource. -#### Step 1: Create the pgBackRest Secret - -pgBackRest is a fundamental part of a PostgreSQL deployment with the PostgreSQL -Operator: not only is it a backup and archive repository, but it also helps with -operations such as self-healing. A PostgreSQL instance a pgBackRest communicate -using ssh, and as such, we need to generate a unique ssh keypair for -communication for each PostgreSQL cluster we deploy. - -In this example, we generate a ssh keypair using ED25519 keys, but if your -environment requires it, you can also use RSA keys. - -In your working directory, run the following commands: - -
-# this variable is the name of the cluster being created
-export pgo_cluster_name=hippo
-# this variable is the namespace the cluster is being deployed into
-export cluster_namespace=pgo
-
-# generate a SSH public/private keypair for use by pgBackRest
-ssh-keygen -t ed25519 -N '' -f "${pgo_cluster_name}-key"
-
-# base64 encoded the keys for the generation of the Kubernetes secret, and place
-# them into variables temporarily
-public_key_temp=$(cat "${pgo_cluster_name}-key.pub" | base64)
-private_key_temp=$(cat "${pgo_cluster_name}-key" | base64)
-export pgbackrest_public_key="${public_key_temp//[$'\n']}" pgbackrest_private_key="${private_key_temp//[$'\n']}"
-
-# create the backrest-repo-config example file and substitute in the newly
-# created keys
-#
-# (Note: that the "config" / "sshd_config" entries contain configuration to
-# ensure that PostgreSQL instances are able to communicate with the pgBackRest
-# repository, which houses backups and archives, and vice versa. Most of the
-# settings follow the sshd defaults, with a few overrides. Edit at your own
-# discretion.)
-cat <<-EOF > "${pgo_cluster_name}-backrest-repo-config.yaml"
-apiVersion: v1
-kind: Secret
-type: Opaque
-metadata:
-  labels:
-    pg-cluster: ${pgo_cluster_name}
-    pgo-backrest-repo: "true"
-  name: ${pgo_cluster_name}-backrest-repo-config
-  namespace: ${cluster_namespace}
-data:
-  authorized_keys: ${pgbackrest_public_key}
-  id_ed25519: ${pgbackrest_private_key}
-  ssh_host_ed25519_key: ${pgbackrest_private_key}
-  config: SG9zdCAqClN0cmljdEhvc3RLZXlDaGVja2luZyBubwpJZGVudGl0eUZpbGUgL3RtcC9pZF9lZDI1NTE5ClBvcnQgMjAyMgpVc2VyIHBnYmFja3Jlc3QK
-  sshd_config: IwkkT3BlbkJTRDogc3NoZF9jb25maWcsdiAxLjEwMCAyMDE2LzA4LzE1IDEyOjMyOjA0IG5hZGR5IEV4cCAkCgojIFRoaXMgaXMgdGhlIHNzaGQgc2VydmVyIHN5c3RlbS13aWRlIGNvbmZpZ3VyYXRpb24gZmlsZS4gIFNlZQojIHNzaGRfY29uZmlnKDUpIGZvciBtb3JlIGluZm9ybWF0aW9uLgoKIyBUaGlzIHNzaGQgd2FzIGNvbXBpbGVkIHdpdGggUEFUSD0vdXNyL2xvY2FsL2JpbjovdXNyL2JpbgoKIyBUaGUgc3RyYXRlZ3kgdXNlZCBmb3Igb3B0aW9ucyBpbiB0aGUgZGVmYXVsdCBzc2hkX2NvbmZpZyBzaGlwcGVkIHdpdGgKIyBPcGVuU1NIIGlzIHRvIHNwZWNpZnkgb3B0aW9ucyB3aXRoIHRoZWlyIGRlZmF1bHQgdmFsdWUgd2hlcmUKIyBwb3NzaWJsZSwgYnV0IGxlYXZlIHRoZW0gY29tbWVudGVkLiAgVW5jb21tZW50ZWQgb3B0aW9ucyBvdmVycmlkZSB0aGUKIyBkZWZhdWx0IHZhbHVlLgoKIyBJZiB5b3Ugd2FudCB0byBjaGFuZ2UgdGhlIHBvcnQgb24gYSBTRUxpbnV4IHN5c3RlbSwgeW91IGhhdmUgdG8gdGVsbAojIFNFTGludXggYWJvdXQgdGhpcyBjaGFuZ2UuCiMgc2VtYW5hZ2UgcG9ydCAtYSAtdCBzc2hfcG9ydF90IC1wIHRjcCAjUE9SVE5VTUJFUgojClBvcnQgMjAyMgojQWRkcmVzc0ZhbWlseSBhbnkKI0xpc3RlbkFkZHJlc3MgMC4wLjAuMAojTGlzdGVuQWRkcmVzcyA6OgoKSG9zdEtleSAvc3NoZC9zc2hfaG9zdF9lZDI1NTE5X2tleQoKIyBDaXBoZXJzIGFuZCBrZXlpbmcKI1Jla2V5TGltaXQgZGVmYXVsdCBub25lCgojIExvZ2dpbmcKI1N5c2xvZ0ZhY2lsaXR5IEFVVEgKU3lzbG9nRmFjaWxpdHkgQVVUSFBSSVYKI0xvZ0xldmVsIElORk8KCiMgQXV0aGVudGljYXRpb246CgojTG9naW5HcmFjZVRpbWUgMm0KUGVybWl0Um9vdExvZ2luIG5vClN0cmljdE1vZGVzIG5vCiNNYXhBdXRoVHJpZXMgNgojTWF4U2Vzc2lvbnMgMTAKClB1YmtleUF1dGhlbnRpY2F0aW9uIHllcwoKIyBUaGUgZGVmYXVsdCBpcyB0byBjaGVjayBib3RoIC5zc2gvYXV0aG9yaXplZF9rZXlzIGFuZCAuc3NoL2F1dGhvcml6ZWRfa2V5czIKIyBidXQgdGhpcyBpcyBvdmVycmlkZGVuIHNvIGluc3RhbGxhdGlvbnMgd2lsbCBvbmx5IGNoZWNrIC5zc2gvYXV0aG9yaXplZF9rZXlzCiNBdXRob3JpemVkS2V5c0ZpbGUJL3BnY29uZi9hdXRob3JpemVkX2tleXMKQXV0aG9yaXplZEtleXNGaWxlCS9zc2hkL2F1dGhvcml6ZWRfa2V5cwoKI0F1dGhvcml6ZWRQcmluY2lwYWxzRmlsZSBub25lCgojQXV0aG9yaXplZEtleXNDb21tYW5kIG5vbmUKI0F1dGhvcml6ZWRLZXlzQ29tbWFuZFVzZXIgbm9ib2R5CgojIEZvciB0aGlzIHRvIHdvcmsgeW91IHdpbGwgYWxzbyBuZWVkIGhvc3Qga2V5cyBpbiAvZXRjL3NzaC9zc2hfa25vd25faG9zdHMKI0hvc3RiYXNlZEF1dGhlbnRpY2F0aW9uIG5vCiMgQ2hhbmdlIHRvIHllcyBpZiB5b3UgZG9uJ3QgdHJ1c3Qgfi8uc3NoL2tub3duX2hvc3RzIGZvcgojIEhvc3RiYXNlZEF1dGhlbnRpY2F0aW9uCiNJZ25vcmVVc2VyS25vd25Ib3N0cyBubwojIERvbid0IHJlYWQgdGhlIHVzZXIncyB+Ly5yaG9zdHMgYW5kIH4vLnNob3N0cyBmaWxlcwojSWdub3JlUmhvc3RzIHllcwoKIyBUbyBkaXNhYmxlIHR1bm5lbGVkIGNsZWFyIHRleHQgcGFzc3dvcmRzLCBjaGFuZ2UgdG8gbm8gaGVyZSEKI1Bhc3N3b3JkQXV0aGVudGljYXRpb24geWVzCiNQZXJtaXRFbXB0eVBhc3N3b3JkcyBubwpQYXNzd29yZEF1dGhlbnRpY2F0aW9uIG5vCgojIENoYW5nZSB0byBubyB0byBkaXNhYmxlIHMva2V5IHBhc3N3b3JkcwpDaGFsbGVuZ2VSZXNwb25zZUF1dGhlbnRpY2F0aW9uIHllcwojQ2hhbGxlbmdlUmVzcG9uc2VBdXRoZW50aWNhdGlvbiBubwoKIyBLZXJiZXJvcyBvcHRpb25zCiNLZXJiZXJvc0F1dGhlbnRpY2F0aW9uIG5vCiNLZXJiZXJvc09yTG9jYWxQYXNzd2QgeWVzCiNLZXJiZXJvc1RpY2tldENsZWFudXAgeWVzCiNLZXJiZXJvc0dldEFGU1Rva2VuIG5vCiNLZXJiZXJvc1VzZUt1c2Vyb2sgeWVzCgojIEdTU0FQSSBvcHRpb25zCiNHU1NBUElBdXRoZW50aWNhdGlvbiB5ZXMKI0dTU0FQSUNsZWFudXBDcmVkZW50aWFscyBubwojR1NTQVBJU3RyaWN0QWNjZXB0b3JDaGVjayB5ZXMKI0dTU0FQSUtleUV4Y2hhbmdlIG5vCiNHU1NBUElFbmFibGVrNXVzZXJzIG5vCgojIFNldCB0aGlzIHRvICd5ZXMnIHRvIGVuYWJsZSBQQU0gYXV0aGVudGljYXRpb24sIGFjY291bnQgcHJvY2Vzc2luZywKIyBhbmQgc2Vzc2lvbiBwcm9jZXNzaW5nLiBJZiB0aGlzIGlzIGVuYWJsZWQsIFBBTSBhdXRoZW50aWNhdGlvbiB3aWxsCiMgYmUgYWxsb3dlZCB0aHJvdWdoIHRoZSBDaGFsbGVuZ2VSZXNwb25zZUF1dGhlbnRpY2F0aW9uIGFuZAojIFBhc3N3b3JkQXV0aGVudGljYXRpb24uICBEZXBlbmRpbmcgb24geW91ciBQQU0gY29uZmlndXJhdGlvbiwKIyBQQU0gYXV0aGVudGljYXRpb24gdmlhIENoYWxsZW5nZVJlc3BvbnNlQXV0aGVudGljYXRpb24gbWF5IGJ5cGFzcwojIHRoZSBzZXR0aW5nIG9mICJQZXJtaXRSb290TG9naW4gd2l0aG91dC1wYXNzd29yZCIuCiMgSWYgeW91IGp1c3Qgd2FudCB0aGUgUEFNIGFjY291bnQgYW5kIHNlc3Npb24gY2hlY2tzIHRvIHJ1biB3aXRob3V0CiMgUEFNIGF1dGhlbnRpY2F0aW9uLCB0aGVuIGVuYWJsZSB0aGlzIGJ1dCBzZXQgUGFzc3dvcmRBdXRoZW50aWNhdGlvbgojIGFuZCBDaGFsbGVuZ2VSZXNwb25zZUF1dGhlbnRpY2F0aW9uIHRvICdubycuCiMgV0FSTklORzogJ1VzZVBBTSBubycgaXMgbm90IHN1cHBvcnRlZCBpbiBSZWQgSGF0IEVudGVycHJpc2UgTGludXggYW5kIG1heSBjYXVzZSBzZXZlcmFsCiMgcHJvYmxlbXMuClVzZVBBTSB5ZXMKCiNBbGxvd0FnZW50Rm9yd2FyZGluZyB5ZXMKI0FsbG93VGNwRm9yd2FyZGluZyB5ZXMKI0dhdGV3YXlQb3J0cyBubwpYMTFGb3J3YXJkaW5nIHllcwojWDExRGlzcGxheU9mZnNldCAxMAojWDExVXNlTG9jYWxob3N0IHllcwojUGVybWl0VFRZIHllcwojUHJpbnRNb3RkIHllcwojUHJpbnRMYXN0TG9nIHllcwojVENQS2VlcEFsaXZlIHllcwojVXNlTG9naW4gbm8KI1Blcm1pdFVzZXJFbnZpcm9ubWVudCBubwojQ29tcHJlc3Npb24gZGVsYXllZAojQ2xpZW50QWxpdmVJbnRlcnZhbCAwCiNDbGllbnRBbGl2ZUNvdW50TWF4IDMKI1Nob3dQYXRjaExldmVsIG5vCiNVc2VETlMgeWVzCiNQaWRGaWxlIC92YXIvcnVuL3NzaGQucGlkCiNNYXhTdGFydHVwcyAxMDozMDoxMDAKI1Blcm1pdFR1bm5lbCBubwojQ2hyb290RGlyZWN0b3J5IG5vbmUKI1ZlcnNpb25BZGRlbmR1bSBub25lCgojIG5vIGRlZmF1bHQgYmFubmVyIHBhdGgKI0Jhbm5lciBub25lCgojIEFjY2VwdCBsb2NhbGUtcmVsYXRlZCBlbnZpcm9ubWVudCB2YXJpYWJsZXMKQWNjZXB0RW52IExBTkcgTENfQ1RZUEUgTENfTlVNRVJJQyBMQ19USU1FIExDX0NPTExBVEUgTENfTU9ORVRBUlkgTENfTUVTU0FHRVMKQWNjZXB0RW52IExDX1BBUEVSIExDX05BTUUgTENfQUREUkVTUyBMQ19URUxFUEhPTkUgTENfTUVBU1VSRU1FTlQKQWNjZXB0RW52IExDX0lERU5USUZJQ0FUSU9OIExDX0FMTCBMQU5HVUFHRQpBY2NlcHRFbnYgWE1PRElGSUVSUwoKIyBvdmVycmlkZSBkZWZhdWx0IG9mIG5vIHN1YnN5c3RlbXMKU3Vic3lzdGVtCXNmdHAJL3Vzci9saWJleGVjL29wZW5zc2gvc2Z0cC1zZXJ2ZXIKCiMgRXhhbXBsZSBvZiBvdmVycmlkaW5nIHNldHRpbmdzIG9uIGEgcGVyLXVzZXIgYmFzaXMKI01hdGNoIFVzZXIgYW5vbmN2cwojCVgxMUZvcndhcmRpbmcgbm8KIwlBbGxvd1RjcEZvcndhcmRpbmcgbm8KIwlQZXJtaXRUVFkgbm8KIwlGb3JjZUNvbW1hbmQgY3ZzIHNlcnZlcgo=
-EOF
-
-# remove the pgBackRest ssh keypair from the shell session
-unset pgbackrest_public_key pgbackrest_private_key
-
-# create the pgBackRest secret
-kubectl apply -f "${pgo_cluster_name}-backrest-repo-config.yaml"
-
- -#### Step 2: Creating the PostgreSQL User Secrets +#### Step 1: Creating the PostgreSQL User Secrets As mentioned above, there are a minimum of three PostgreSQL user accounts that you must create in order to bootstrap a PostgreSQL cluster. These are: @@ -354,7 +293,7 @@ kubectl label secrets -n "${cluster_namespace}" "${pgo_cluster_name}-primaryuser kubectl label secrets -n "${cluster_namespace}" "${pgo_cluster_name}-hippo-secret" "pg-cluster=${pgo_cluster_name}" ``` -#### Step 3: Create the PostgreSQL Cluster +#### Step 2: Create the PostgreSQL Cluster With the Secrets in place. It is now time to create the PostgreSQL cluster. diff --git a/examples/create-by-resource/run.sh b/examples/create-by-resource/run.sh index 1cdefdda77..ea034a4fe2 100755 --- a/examples/create-by-resource/run.sh +++ b/examples/create-by-resource/run.sh @@ -41,49 +41,10 @@ rm $DIR/fromcrd-key $DIR/fromcrd-key.pub # EXAMPLE RUN # ############### -# generate a SSH public/private keypair for use by pgBackRest -ssh-keygen -t ed25519 -N '' -f $DIR/fromcrd-key - -# base64 encoded the keys for the generation of the Kube secret, and place -# them into variables temporarily -PUBLIC_KEY_TEMP=$(cat $DIR/fromcrd-key.pub | base64) -PRIVATE_KEY_TEMP=$(cat $DIR/fromcrd-key | base64) - -export PUBLIC_KEY="${PUBLIC_KEY_TEMP//[$'\n']}" -export PRIVATE_KEY="${PRIVATE_KEY_TEMP//[$'\n']}" - -unset PUBLIC_KEY_TEMP -unset PRIVATE_KEY_TEMP - -# create the backrest-repo-config example file and substitute in the newly -# created keys -cat <<-EOF > $DIR/backrest-repo-config.yaml -apiVersion: v1 -data: - authorized_keys: ${PUBLIC_KEY} - id_ed25519: ${PRIVATE_KEY} - ssh_host_ed25519_key: ${PRIVATE_KEY} - config: SG9zdCAqClN0cmljdEhvc3RLZXlDaGVja2luZyBubwpJZGVudGl0eUZpbGUgL3RtcC9pZF9lZDI1NTE5ClBvcnQgMjAyMgpVc2VyIHBnYmFja3Jlc3QK - sshd_config: IwkkT3BlbkJTRDogc3NoZF9jb25maWcsdiAxLjEwMCAyMDE2LzA4LzE1IDEyOjMyOjA0IG5hZGR5IEV4cCAkCgojIFRoaXMgaXMgdGhlIHNzaGQgc2VydmVyIHN5c3RlbS13aWRlIGNvbmZpZ3VyYXRpb24gZmlsZS4gIFNlZQojIHNzaGRfY29uZmlnKDUpIGZvciBtb3JlIGluZm9ybWF0aW9uLgoKIyBUaGlzIHNzaGQgd2FzIGNvbXBpbGVkIHdpdGggUEFUSD0vdXNyL2xvY2FsL2JpbjovdXNyL2JpbgoKIyBUaGUgc3RyYXRlZ3kgdXNlZCBmb3Igb3B0aW9ucyBpbiB0aGUgZGVmYXVsdCBzc2hkX2NvbmZpZyBzaGlwcGVkIHdpdGgKIyBPcGVuU1NIIGlzIHRvIHNwZWNpZnkgb3B0aW9ucyB3aXRoIHRoZWlyIGRlZmF1bHQgdmFsdWUgd2hlcmUKIyBwb3NzaWJsZSwgYnV0IGxlYXZlIHRoZW0gY29tbWVudGVkLiAgVW5jb21tZW50ZWQgb3B0aW9ucyBvdmVycmlkZSB0aGUKIyBkZWZhdWx0IHZhbHVlLgoKIyBJZiB5b3Ugd2FudCB0byBjaGFuZ2UgdGhlIHBvcnQgb24gYSBTRUxpbnV4IHN5c3RlbSwgeW91IGhhdmUgdG8gdGVsbAojIFNFTGludXggYWJvdXQgdGhpcyBjaGFuZ2UuCiMgc2VtYW5hZ2UgcG9ydCAtYSAtdCBzc2hfcG9ydF90IC1wIHRjcCAjUE9SVE5VTUJFUgojClBvcnQgMjAyMgojQWRkcmVzc0ZhbWlseSBhbnkKI0xpc3RlbkFkZHJlc3MgMC4wLjAuMAojTGlzdGVuQWRkcmVzcyA6OgoKSG9zdEtleSAvc3NoZC9zc2hfaG9zdF9lZDI1NTE5X2tleQoKIyBDaXBoZXJzIGFuZCBrZXlpbmcKI1Jla2V5TGltaXQgZGVmYXVsdCBub25lCgojIExvZ2dpbmcKI1N5c2xvZ0ZhY2lsaXR5IEFVVEgKU3lzbG9nRmFjaWxpdHkgQVVUSFBSSVYKI0xvZ0xldmVsIElORk8KCiMgQXV0aGVudGljYXRpb246CgojTG9naW5HcmFjZVRpbWUgMm0KUGVybWl0Um9vdExvZ2luIG5vClN0cmljdE1vZGVzIG5vCiNNYXhBdXRoVHJpZXMgNgojTWF4U2Vzc2lvbnMgMTAKClB1YmtleUF1dGhlbnRpY2F0aW9uIHllcwoKIyBUaGUgZGVmYXVsdCBpcyB0byBjaGVjayBib3RoIC5zc2gvYXV0aG9yaXplZF9rZXlzIGFuZCAuc3NoL2F1dGhvcml6ZWRfa2V5czIKIyBidXQgdGhpcyBpcyBvdmVycmlkZGVuIHNvIGluc3RhbGxhdGlvbnMgd2lsbCBvbmx5IGNoZWNrIC5zc2gvYXV0aG9yaXplZF9rZXlzCkF1dGhvcml6ZWRLZXlzRmlsZQkvc3NoZC9hdXRob3JpemVkX2tleXMKCiNBdXRob3JpemVkUHJpbmNpcGFsc0ZpbGUgbm9uZQoKI0F1dGhvcml6ZWRLZXlzQ29tbWFuZCBub25lCiNBdXRob3JpemVkS2V5c0NvbW1hbmRVc2VyIG5vYm9keQoKIyBGb3IgdGhpcyB0byB3b3JrIHlvdSB3aWxsIGFsc28gbmVlZCBob3N0IGtleXMgaW4gL2V0Yy9zc2gvc3NoX2tub3duX2hvc3RzCiNIb3N0YmFzZWRBdXRoZW50aWNhdGlvbiBubwojIENoYW5nZSB0byB5ZXMgaWYgeW91IGRvbid0IHRydXN0IH4vLnNzaC9rbm93bl9ob3N0cyBmb3IKIyBIb3N0YmFzZWRBdXRoZW50aWNhdGlvbgojSWdub3JlVXNlcktub3duSG9zdHMgbm8KIyBEb24ndCByZWFkIHRoZSB1c2VyJ3Mgfi8ucmhvc3RzIGFuZCB+Ly5zaG9zdHMgZmlsZXMKI0lnbm9yZVJob3N0cyB5ZXMKCiMgVG8gZGlzYWJsZSB0dW5uZWxlZCBjbGVhciB0ZXh0IHBhc3N3b3JkcywgY2hhbmdlIHRvIG5vIGhlcmUhCiNQYXNzd29yZEF1dGhlbnRpY2F0aW9uIHllcwojUGVybWl0RW1wdHlQYXNzd29yZHMgbm8KUGFzc3dvcmRBdXRoZW50aWNhdGlvbiBubwoKIyBDaGFuZ2UgdG8gbm8gdG8gZGlzYWJsZSBzL2tleSBwYXNzd29yZHMKQ2hhbGxlbmdlUmVzcG9uc2VBdXRoZW50aWNhdGlvbiB5ZXMKI0NoYWxsZW5nZVJlc3BvbnNlQXV0aGVudGljYXRpb24gbm8KCiMgS2VyYmVyb3Mgb3B0aW9ucwojS2VyYmVyb3NBdXRoZW50aWNhdGlvbiBubwojS2VyYmVyb3NPckxvY2FsUGFzc3dkIHllcwojS2VyYmVyb3NUaWNrZXRDbGVhbnVwIHllcwojS2VyYmVyb3NHZXRBRlNUb2tlbiBubwojS2VyYmVyb3NVc2VLdXNlcm9rIHllcwoKIyBHU1NBUEkgb3B0aW9ucwojR1NTQVBJQXV0aGVudGljYXRpb24geWVzCiNHU1NBUElDbGVhbnVwQ3JlZGVudGlhbHMgbm8KI0dTU0FQSVN0cmljdEFjY2VwdG9yQ2hlY2sgeWVzCiNHU1NBUElLZXlFeGNoYW5nZSBubwojR1NTQVBJRW5hYmxlazV1c2VycyBubwoKIyBTZXQgdGhpcyB0byAneWVzJyB0byBlbmFibGUgUEFNIGF1dGhlbnRpY2F0aW9uLCBhY2NvdW50IHByb2Nlc3NpbmcsCiMgYW5kIHNlc3Npb24gcHJvY2Vzc2luZy4gSWYgdGhpcyBpcyBlbmFibGVkLCBQQU0gYXV0aGVudGljYXRpb24gd2lsbAojIGJlIGFsbG93ZWQgdGhyb3VnaCB0aGUgQ2hhbGxlbmdlUmVzcG9uc2VBdXRoZW50aWNhdGlvbiBhbmQKIyBQYXNzd29yZEF1dGhlbnRpY2F0aW9uLiAgRGVwZW5kaW5nIG9uIHlvdXIgUEFNIGNvbmZpZ3VyYXRpb24sCiMgUEFNIGF1dGhlbnRpY2F0aW9uIHZpYSBDaGFsbGVuZ2VSZXNwb25zZUF1dGhlbnRpY2F0aW9uIG1heSBieXBhc3MKIyB0aGUgc2V0dGluZyBvZiAiUGVybWl0Um9vdExvZ2luIHdpdGhvdXQtcGFzc3dvcmQiLgojIElmIHlvdSBqdXN0IHdhbnQgdGhlIFBBTSBhY2NvdW50IGFuZCBzZXNzaW9uIGNoZWNrcyB0byBydW4gd2l0aG91dAojIFBBTSBhdXRoZW50aWNhdGlvbiwgdGhlbiBlbmFibGUgdGhpcyBidXQgc2V0IFBhc3N3b3JkQXV0aGVudGljYXRpb24KIyBhbmQgQ2hhbGxlbmdlUmVzcG9uc2VBdXRoZW50aWNhdGlvbiB0byAnbm8nLgojIFdBUk5JTkc6ICdVc2VQQU0gbm8nIGlzIG5vdCBzdXBwb3J0ZWQgaW4gUmVkIEhhdCBFbnRlcnByaXNlIExpbnV4IGFuZCBtYXkgY2F1c2Ugc2V2ZXJhbAojIHByb2JsZW1zLgpVc2VQQU0geWVzIAoKI0FsbG93QWdlbnRGb3J3YXJkaW5nIHllcwojQWxsb3dUY3BGb3J3YXJkaW5nIHllcwojR2F0ZXdheVBvcnRzIG5vClgxMUZvcndhcmRpbmcgeWVzCiNYMTFEaXNwbGF5T2Zmc2V0IDEwCiNYMTFVc2VMb2NhbGhvc3QgeWVzCiNQZXJtaXRUVFkgeWVzCiNQcmludE1vdGQgeWVzCiNQcmludExhc3RMb2cgeWVzCiNUQ1BLZWVwQWxpdmUgeWVzCiNVc2VMb2dpbiBubwpVc2VQcml2aWxlZ2VTZXBhcmF0aW9uIG5vCiNQZXJtaXRVc2VyRW52aXJvbm1lbnQgbm8KI0NvbXByZXNzaW9uIGRlbGF5ZWQKI0NsaWVudEFsaXZlSW50ZXJ2YWwgMAojQ2xpZW50QWxpdmVDb3VudE1heCAzCiNTaG93UGF0Y2hMZXZlbCBubwojVXNlRE5TIHllcwojUGlkRmlsZSAvdmFyL3J1bi9zc2hkLnBpZAojTWF4U3RhcnR1cHMgMTA6MzA6MTAwCiNQZXJtaXRUdW5uZWwgbm8KI0Nocm9vdERpcmVjdG9yeSBub25lCiNWZXJzaW9uQWRkZW5kdW0gbm9uZQoKIyBubyBkZWZhdWx0IGJhbm5lciBwYXRoCiNCYW5uZXIgbm9uZQoKIyBBY2NlcHQgbG9jYWxlLXJlbGF0ZWQgZW52aXJvbm1lbnQgdmFyaWFibGVzCkFjY2VwdEVudiBMQU5HIExDX0NUWVBFIExDX05VTUVSSUMgTENfVElNRSBMQ19DT0xMQVRFIExDX01PTkVUQVJZIExDX01FU1NBR0VTCkFjY2VwdEVudiBMQ19QQVBFUiBMQ19OQU1FIExDX0FERFJFU1MgTENfVEVMRVBIT05FIExDX01FQVNVUkVNRU5UCkFjY2VwdEVudiBMQ19JREVOVElGSUNBVElPTiBMQ19BTEwgTEFOR1VBR0UKQWNjZXB0RW52IFhNT0RJRklFUlMKCiMgb3ZlcnJpZGUgZGVmYXVsdCBvZiBubyBzdWJzeXN0ZW1zClN1YnN5c3RlbQlzZnRwCS91c3IvbGliZXhlYy9vcGVuc3NoL3NmdHAtc2VydmVyCgojIEV4YW1wbGUgb2Ygb3ZlcnJpZGluZyBzZXR0aW5ncyBvbiBhIHBlci11c2VyIGJhc2lzCiNNYXRjaCBVc2VyIGFub25jdnMKIwlYMTFGb3J3YXJkaW5nIG5vCiMJQWxsb3dUY3BGb3J3YXJkaW5nIG5vCiMJUGVybWl0VFRZIG5vCiMJRm9yY2VDb21tYW5kIGN2cyBzZXJ2ZXI= -kind: Secret -metadata: - labels: - pg-cluster: fromcrd - pgo-backrest-repo: "true" - name: fromcrd-backrest-repo-config - namespace: ${NS} -type: Opaque -EOF - -# unset the *_KEY environmental variables -unset PUBLIC_KEY -unset PRIVATE_KEY - # create the required postgres credentials for the fromcrd cluster $PGO_CMD -n $NS create -f $DIR/postgres-secret.yaml $PGO_CMD -n $NS create -f $DIR/primaryuser-secret.yaml $PGO_CMD -n $NS create -f $DIR/testuser-secret.yaml -$PGO_CMD -n $NS create -f $DIR/backrest-repo-config.yaml # create the pgcluster CRD for the fromcrd cluster $PGO_CMD -n $NS create -f $DIR/fromcrd.json diff --git a/examples/helm/README.md b/examples/helm/README.md index 09d06cbeb3..390bfbbaae 100644 --- a/examples/helm/README.md +++ b/examples/helm/README.md @@ -1,23 +1,32 @@ # create-cluster This is a working example of how to create a cluster via the crd workflow -using a helm chart +using a [Helm](https://helm.sh/) chart. + +## Prerequisites + +### Postgres Operator -## Assumptions This example assumes you have the Crunchy PostgreSQL Operator installed -in a namespace called pgo. +in a namespace called `pgo`. + +### Helm -## Helm Helm will also need to be installed for this example to run -## Documenation +## Documentation + Please see the documentation for more guidance using custom resources: https://access.crunchydata.com/documentation/postgres-operator/latest/custom-resources/ +## Setup + +If you are running Postgres Operator 4.5.1 or later, you can skip the below +step. + +### Before 4.5.1 -## Example set up and execution -create a certs directy and generate certs ``` cd postgres-operator/examples/helm/create-cluster @@ -29,27 +38,32 @@ export pgo_cluster_name=hippo # generate a SSH public/private keypair for use by pgBackRest ssh-keygen -t ed25519 -N '' -f "${pgo_cluster_name}-key" - ``` -For this example we will deploy the cluster into the pgo -namespace where the opertor is installed and running. -return to the create-cluster directory +## Running the Example + +For this example we will deploy the cluster into the `pgo` namespace where the +Postgres Operator is installed and running. + +Return to the `create-cluster` directory: + ``` cd postgres-operator/examples/helm/create-cluster ``` -The following commands will allow you to execute a dry run first with debug -if you want to verify everthing is set correctly. Then after everything looks good -run the install command with out the flags +The following commands will allow you to execute a dry run first with debug +if you want to verify everything is set correctly. Then after everything looks +good run the install command with out the flags: + ``` helm install --dry-run --debug postgres-operator-create-cluster . -n pgo - helm install postgres-operator-create-cluster . -n pgo ``` + ## Verify -Now you can your Hippo cluster has deployed into the pgo -namespace by running these few commands + +Now you can your Hippo cluster has deployed into the pgo namespace by running +these few commands: ``` kubectl get all -n pgo @@ -58,7 +72,8 @@ pgo test hippo -n pgo pgo show cluster hippo -n pgo ``` + ## NOTE -As of operator version 4.5.0 when using helm uninstall you will have to manually -clean up some left over artifacts afer running the unistall +As of operator version 4.5.0 when using helm uninstall you will have to manually +clean up some left over artifacts after running the uninstall. diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index 67eaea056c..37ce1f0eab 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -1001,17 +1001,25 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. backrestS3CACert = backrestSecret.Data[util.BackRestRepoSecretKeyAWSS3KeyAWSS3CACert] } - err := util.CreateBackrestRepoSecrets(apiserver.Clientset, - util.BackrestRepoConfig{ - BackrestS3CA: backrestS3CACert, - BackrestS3Key: request.BackrestS3Key, - BackrestS3KeySecret: request.BackrestS3KeySecret, - ClusterName: clusterName, - ClusterNamespace: request.Namespace, - OperatorNamespace: apiserver.PgoNamespace, - }) - - if err != nil { + // set up the secret for the cluster that contains the pgBackRest + // information + secret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Labels: map[string]string{ + config.LABEL_VENDOR: config.LABEL_CRUNCHY, + config.LABEL_PG_CLUSTER: clusterName, + config.LABEL_PGO_BACKREST_REPO: "true", + }, + }, + Data: map[string][]byte{ + util.BackRestRepoSecretKeyAWSS3KeyAWSS3CACert: backrestS3CACert, + util.BackRestRepoSecretKeyAWSS3KeyAWSS3Key: []byte(request.BackrestS3Key), + util.BackRestRepoSecretKeyAWSS3KeyAWSS3KeySecret: []byte(request.BackrestS3KeySecret), + }, + } + + if _, err := apiserver.Clientset.CoreV1().Secrets(ns).Create(ctx, secret, metav1.CreateOptions{}); err != nil && !kubeapi.IsAlreadyExists(err) { resp.Status.Code = msgs.Error resp.Status.Msg = fmt.Sprintf("could not create backrest repo secret: %s", err) return resp diff --git a/internal/operator/backrest/repo.go b/internal/operator/backrest/repo.go index 68c1152056..e53427fd1d 100644 --- a/internal/operator/backrest/repo.go +++ b/internal/operator/backrest/repo.go @@ -159,6 +159,19 @@ func CreateRepoDeployment(clientset kubernetes.Interface, cluster *crv1.Pgcluste return nil } +// CreateRepoSecret allows for the creation of the Secret used to populate +// some (mostly) sensitive fields for managing the pgBackRest repository. +// +// If the Secret already exists, then missing fields will be overwritten. +func CreateRepoSecret(clientset kubernetes.Interface, cluster *crv1.Pgcluster) error { + return util.CreateBackrestRepoSecrets(clientset, + util.BackrestRepoConfig{ + ClusterName: cluster.Name, + ClusterNamespace: cluster.Namespace, + OperatorNamespace: operator.PgoNamespace, + }) +} + // setBootstrapRepoOverrides overrides certain fields used to populate the pgBackRest repository template // as needed to support the creation of a bootstrap repository need to bootstrap a new cluster from an // existing data source. diff --git a/internal/operator/cluster/cluster.go b/internal/operator/cluster/cluster.go index 474e92a52a..651cba0aa6 100644 --- a/internal/operator/cluster/cluster.go +++ b/internal/operator/cluster/cluster.go @@ -89,6 +89,14 @@ func AddClusterBase(clientset kubeapi.Interface, cl *crv1.Pgcluster, namespace s return } + // ensure the the pgBackRest Secret is created. If this fails, we have to + // abort + if err := backrest.CreateRepoSecret(clientset, cl); err != nil { + log.Error(err) + publishClusterCreateFailure(cl, err.Error()) + return + } + if err := annotateBackrestSecret(clientset, cl); err != nil { log.Error(err) publishClusterCreateFailure(cl, err.Error()) diff --git a/internal/util/cluster.go b/internal/util/cluster.go index b0b72ea6dd..185bc04035 100644 --- a/internal/util/cluster.go +++ b/internal/util/cluster.go @@ -28,6 +28,7 @@ import ( log "github.com/sirupsen/logrus" v1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" @@ -117,72 +118,112 @@ func CreateBackrestRepoSecrets(clientset kubernetes.Interface, backrestRepoConfig BackrestRepoConfig) error { ctx := context.TODO() - keys, err := NewPrivatePublicKeyPair() - if err != nil { - return err + // first: determine if a Secret already exists. If it does, we are going to + // work on modifying that Secret. + secretName := fmt.Sprintf("%s-%s", backrestRepoConfig.ClusterName, + config.LABEL_BACKREST_REPO_SECRET) + secret, secretErr := clientset.CoreV1().Secrets(backrestRepoConfig.ClusterNamespace).Get( + ctx, secretName, metav1.GetOptions{}) + + // only return an error if this is a **not** a not found error + if secretErr != nil && !kerrors.IsNotFound(secretErr) { + log.Error(secretErr) + return secretErr + } + + // determine if we need to create a new secret, i.e. this is a not found error + newSecret := secretErr != nil + if newSecret { + // set up the secret for the cluster that contains the pgBackRest information + secret = &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Labels: map[string]string{ + config.LABEL_VENDOR: config.LABEL_CRUNCHY, + config.LABEL_PG_CLUSTER: backrestRepoConfig.ClusterName, + config.LABEL_PGO_BACKREST_REPO: "true", + }, + }, + Data: map[string][]byte{}, + } } - // Retrieve the S3/SSHD configuration files from secret - configs, err := clientset. + // next, load the Operator level pgBackRest secret templates, which contain + // SSHD(...?) and possible S3 credentials + configs, configErr := clientset. CoreV1().Secrets(backrestRepoConfig.OperatorNamespace). Get(ctx, "pgo-backrest-repo-config", metav1.GetOptions{}) - if err != nil { - log.Error(err) - return err + if configErr != nil { + log.Error(configErr) + return configErr + } + + // set the SSH/SSHD configuration, if it is not presently set + for _, key := range []string{backRestRepoSecretKeySSHConfig, backRestRepoSecretKeySSHDConfig} { + if len(secret.Data[key]) == 0 { + secret.Data[key] = configs.Data[key] + } } - // if an S3 key has been provided via the request, then use key and key secret - // included in the request instead of the default credentials that are - // available in the Operator pgBackRest secret - backrestS3Key := []byte(backrestRepoConfig.BackrestS3Key) + // set the SSH keys if any appear to be unset + if len(secret.Data[backRestRepoSecretKeyAuthorizedKeys]) == 0 || + len(secret.Data[backRestRepoSecretKeySSHPrivateKey]) == 0 || + len(secret.Data[backRestRepoSecretKeySSHHostPrivateKey]) == 0 { + // generate the keypair and then assign it to the values in the Secret + keys, keyErr := NewPrivatePublicKeyPair() + + if keyErr != nil { + log.Error(keyErr) + return keyErr + } + + secret.Data[backRestRepoSecretKeyAuthorizedKeys] = keys.Public + secret.Data[backRestRepoSecretKeySSHPrivateKey] = keys.Private + secret.Data[backRestRepoSecretKeySSHHostPrivateKey] = keys.Private + } - if backrestRepoConfig.BackrestS3Key == "" { - backrestS3Key = configs.Data[BackRestRepoSecretKeyAWSS3KeyAWSS3Key] + // Set the S3 credentials + // If explicit S3 credentials are passed in, use those. + // If the Secret already has S3 credentials, use those. + // Otherwise, try to load in the default credentials from the Operator Secret. + if len(backrestRepoConfig.BackrestS3CA) != 0 { + secret.Data[BackRestRepoSecretKeyAWSS3KeyAWSS3CACert] = backrestRepoConfig.BackrestS3CA } - backrestS3KeySecret := []byte(backrestRepoConfig.BackrestS3KeySecret) + if len(secret.Data[BackRestRepoSecretKeyAWSS3KeyAWSS3CACert]) == 0 && + len(configs.Data[BackRestRepoSecretKeyAWSS3KeyAWSS3CACert]) != 0 { + secret.Data[BackRestRepoSecretKeyAWSS3KeyAWSS3CACert] = configs.Data[BackRestRepoSecretKeyAWSS3KeyAWSS3CACert] + } - if backrestRepoConfig.BackrestS3KeySecret == "" { - backrestS3KeySecret = configs.Data[BackRestRepoSecretKeyAWSS3KeyAWSS3KeySecret] + if backrestRepoConfig.BackrestS3Key != "" { + secret.Data[BackRestRepoSecretKeyAWSS3KeyAWSS3Key] = []byte(backrestRepoConfig.BackrestS3Key) } - // determine if there is a CA override provided, and if not, use the default - // from the configuration - caCert := backrestRepoConfig.BackrestS3CA - if len(caCert) == 0 { - caCert = configs.Data[BackRestRepoSecretKeyAWSS3KeyAWSS3CACert] + if len(secret.Data[BackRestRepoSecretKeyAWSS3KeyAWSS3Key]) == 0 && + len(configs.Data[BackRestRepoSecretKeyAWSS3KeyAWSS3Key]) != 0 { + secret.Data[BackRestRepoSecretKeyAWSS3KeyAWSS3Key] = configs.Data[BackRestRepoSecretKeyAWSS3KeyAWSS3Key] } - // set up the secret for the cluster that contains the pgBackRest information - secret := v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-%s", backrestRepoConfig.ClusterName, - config.LABEL_BACKREST_REPO_SECRET), - Labels: map[string]string{ - config.LABEL_VENDOR: config.LABEL_CRUNCHY, - config.LABEL_PG_CLUSTER: backrestRepoConfig.ClusterName, - config.LABEL_PGO_BACKREST_REPO: "true", - }, - }, - Data: map[string][]byte{ - BackRestRepoSecretKeyAWSS3KeyAWSS3CACert: caCert, - BackRestRepoSecretKeyAWSS3KeyAWSS3Key: backrestS3Key, - BackRestRepoSecretKeyAWSS3KeyAWSS3KeySecret: backrestS3KeySecret, - backRestRepoSecretKeyAuthorizedKeys: keys.Public, - backRestRepoSecretKeySSHConfig: configs.Data[backRestRepoSecretKeySSHConfig], - backRestRepoSecretKeySSHDConfig: configs.Data[backRestRepoSecretKeySSHDConfig], - backRestRepoSecretKeySSHPrivateKey: keys.Private, - backRestRepoSecretKeySSHHostPrivateKey: keys.Private, - }, + if backrestRepoConfig.BackrestS3KeySecret != "" { + secret.Data[BackRestRepoSecretKeyAWSS3KeyAWSS3KeySecret] = []byte(backrestRepoConfig.BackrestS3KeySecret) } - _, err = clientset.CoreV1().Secrets(backrestRepoConfig.ClusterNamespace). - Create(ctx, &secret, metav1.CreateOptions{}) - if kubeapi.IsAlreadyExists(err) { - _, err = clientset.CoreV1().Secrets(backrestRepoConfig.ClusterNamespace). - Update(ctx, &secret, metav1.UpdateOptions{}) + if len(secret.Data[BackRestRepoSecretKeyAWSS3KeyAWSS3KeySecret]) == 0 && + len(configs.Data[BackRestRepoSecretKeyAWSS3KeyAWSS3KeySecret]) != 0 { + secret.Data[BackRestRepoSecretKeyAWSS3KeyAWSS3KeySecret] = configs.Data[BackRestRepoSecretKeyAWSS3KeyAWSS3KeySecret] } + + // time to create or update the secret! + if newSecret { + _, err := clientset.CoreV1().Secrets(backrestRepoConfig.ClusterNamespace).Create( + ctx, secret, metav1.CreateOptions{}) + return err + } + + _, err := clientset.CoreV1().Secrets(backrestRepoConfig.ClusterNamespace).Update( + ctx, secret, metav1.UpdateOptions{}) + return err } From cc2aa0cc648d4f38c85397b601a8bf415d10f634 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 27 Oct 2020 15:03:32 -0400 Subject: [PATCH 002/276] Reorder custom resource documentation Move the attributes to the latter half of the page, and showcase the workflows at the top. --- docs/content/custom-resources/_index.md | 396 ++++++++++++------------ 1 file changed, 198 insertions(+), 198 deletions(-) diff --git a/docs/content/custom-resources/_index.md b/docs/content/custom-resources/_index.md index af913755e9..7e024900f4 100644 --- a/docs/content/custom-resources/_index.md +++ b/docs/content/custom-resources/_index.md @@ -35,204 +35,6 @@ need to interface through the [`pgo` client]({{< relref "/pgo-client/_index.md" The following sections will describe the functionality that is available today when manipulating the PostgreSQL Operator Custom Resources directly. -## PostgreSQL Operator Custom Resource Definitions - -There are several PostgreSQL Operator Custom Resource Definitions (CRDs) that -are installed in order for the PostgreSQL Operator to successfully function: - -- `pgclusters.crunchydata.com`: Stores information required to manage a -PostgreSQL cluster. This includes things like the cluster name, what storage and -resource classes to use, which version of PostgreSQL to run, information about -how to maintain a high-availability cluster, etc. -- `pgreplicas.crunchydata.com`: Stores information required to manage the -replicas within a PostgreSQL cluster. This includes things like the number of -replicas, what storage and resource classes to use, special affinity rules, etc. -- `pgtasks.crunchydata.com`: A general purpose CRD that accepts a type of task -that is needed to run against a cluster (e.g. take a backup) and tracks the -state of said task through its workflow. -- `pgpolicies.crunchydata.com`: Stores a reference to a SQL file that can be -executed against a PostgreSQL cluster. In the past, this was used to manage RLS -policies on PostgreSQL clusters. - -Below takes an in depth look for what each attribute does in a Custom Resource -Definition, and how they can be used in the creation and update workflow. - -### Glossary - -- `create`: if an attribute is listed as `create`, it means it can affect what -happens when a new Custom Resource is created. -- `update`: if an attribute is listed as `update`, it means it can affect the -Custom Resource, and by extension the objects it manages, when the attribute is -updated. - -### `pgclusters.crunchydata.com` - -The `pgclusters.crunchydata.com` Custom Resource Definition is the fundamental -definition of a PostgreSQL cluster. Most attributes only affect the deployment -of a PostgreSQL cluster at the time the PostgreSQL cluster is created. Some -attributes can be modified during the lifetime of the PostgreSQL cluster and -make changes, as described below. - -#### Specification (`Spec`) - -| Attribute | Action | Description | -|-----------|--------|-------------| -| Annotations | `create`, `update` | Specify Kubernetes [Annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) that can be applied to the different deployments managed by the PostgreSQL Operator (PostgreSQL, pgBackRest, pgBouncer). For more information, please see the "Annotations Specification" below. | -| BackrestConfig | `create` | Optional references to pgBackRest configuration files -| BackrestLimits | `create`, `update` | Specify the container resource limits that the pgBackRest repository should use. Follows the [Kubernetes definitions of resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | -| BackrestResources | `create`, `update` | Specify the container resource requests that the pgBackRest repository should use. Follows the [Kubernetes definitions of resource requests](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | -| BackrestS3Bucket | `create` | An optional parameter that specifies a S3 bucket that pgBackRest should use. | -| BackrestS3Endpoint | `create` | An optional parameter that specifies the S3 endpoint pgBackRest should use. | -| BackrestS3Region | `create` | An optional parameter that specifies a cloud region that pgBackRest should use. | -| BackrestS3URIStyle | `create` | An optional parameter that specifies if pgBackRest should use the `path` or `host` S3 URI style. | -| BackrestS3VerifyTLS | `create` | An optional parameter that specifies if pgBackRest should verify the TLS endpoint. | -| BackrestStorage | `create` | A specification that gives information about the storage attributes for the pgBackRest repository, which stores backups and archives, of the PostgreSQL cluster. For details, please see the `Storage Specification` section below. This is required. | -| CCPImage | `create` | The name of the PostgreSQL container image to use, e.g. `crunchy-postgres-ha` or `crunchy-postgres-ha-gis`. | -| CCPImagePrefix | `create` | If provided, the image prefix (or registry) of the PostgreSQL container image, e.g. `registry.developers.crunchydata.com/crunchydata`. The default is to use the image prefix set in the PostgreSQL Operator configuration. | -| CCPImageTag | `create` | The tag of the PostgreSQL container image to use, e.g. `{{< param centosBase >}}-{{< param postgresVersion >}}-{{< param operatorVersion >}}`. | -| CollectSecretName | `create` | An optional attribute unless `crunchy-postgres-exporter` is specified in the `UserLabels`; contains the name of a Kubernetes Secret that contains the credentials for a PostgreSQL user that is used for metrics collection, and is created when the PostgreSQL cluster is first bootstrapped. For more information, please see `User Secret Specification`.| -| ClusterName | `create` | The name of the PostgreSQL cluster, e.g. `hippo`. This is used to group PostgreSQL instances (primary, replicas) together. | -| CustomConfig | `create` | If specified, references a custom ConfigMap to use when bootstrapping a PostgreSQL cluster. For the shape of this file, please see the section on [Custom Configuration]({{< relref "/advanced/custom-configuration.md" >}}) | -| Database | `create` | The name of a database that the PostgreSQL user can log into after the PostgreSQL cluster is created. | -| ExporterLimits | `create`, `update` | Specify the container resource limits that the `crunchy-postgres-exporter` sidecar uses when it is deployed with a PostgreSQL instance. Follows the [Kubernetes definitions of resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | -| ExporterPort | `create` | If the `"crunchy-postgres-exporter"` label is set in `UserLabels`, then this specifies the port that the metrics sidecar runs on (e.g. `9187`) | -| ExporterResources | `create`, `update` | Specify the container resource requests that the `crunchy-postgres-exporter` sidecar uses when it is deployed with a PostgreSQL instance. Follows the [Kubernetes definitions of resource requests](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | -| Limits | `create`, `update` | Specify the container resource limits that the PostgreSQL cluster should use. Follows the [Kubernetes definitions of resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | -| Name | `create` | The name of the PostgreSQL instance that is the primary. On creation, this should be set to be the same as `ClusterName`. | -| Namespace | `create` | The Kubernetes Namespace that the PostgreSQL cluster is deployed in. | -| PGBadgerPort | `create` | If the `"crunchy-pgbadger"` label is set in `UserLabels`, then this specifies the port that the pgBadger sidecar runs on (e.g. `10000`) | -| PGDataSource | `create` | Used to indicate if a PostgreSQL cluster should bootstrap its data from a pgBackRest repository. This uses the PostgreSQL Data Source Specification, described below. | -| PGOImagePrefix | `create` | If provided, the image prefix (or registry) of any PostgreSQL Operator images that are used for jobs, e.g. `registry.developers.crunchydata.com/crunchydata`. The default is to use the image prefix set in the PostgreSQL Operator configuration. | -| PgBouncer | `create`, `update` | If specified, defines the attributes to use for the pgBouncer connection pooling deployment that can be used in conjunction with this PostgreSQL cluster. Please see the specification defined below. | -| PodAntiAffinity | `create` | A required section. Sets the [pod anti-affinity rules]({{< relref "/architecture/high-availability/_index.md#how-the-crunchy-postgresql-operator-uses-pod-anti-affinity" >}}) for the PostgreSQL cluster and associated deployments. Please see the `Pod Anti-Affinity Specification` section below. | -| Policies | `create` | If provided, a comma-separated list referring to `pgpolicies.crunchydata.com.Spec.Name` that should be run once the PostgreSQL primary is first initialized. | -| Port | `create` | The port that PostgreSQL will run on, e.g. `5432`. | -| PrimaryStorage | `create` | A specification that gives information about the storage attributes for the primary instance in the PostgreSQL cluster. For details, please see the `Storage Specification` section below. This is required. | -| RootSecretName | `create` | The name of a Kubernetes Secret that contains the credentials for a PostgreSQL _replication user_ that is created when the PostgreSQL cluster is first bootstrapped. For more information, please see `User Secret Specification`.| -| ReplicaStorage | `create` | A specification that gives information about the storage attributes for any replicas in the PostgreSQL cluster. For details, please see the `Storage Specification` section below. This will likely be changed in the future based on the nature of the high-availability system, but presently it is still required that you set it. It is recommended you use similar settings to that of `PrimaryStorage`. | -| Replicas | `create` | The number of replicas to create after a PostgreSQL primary is first initialized. This only works on create; to scale a cluster after it is initialized, please use the [`pgo scale`]({{< relref "/pgo-client/reference/pgo_scale.md" >}}) command. | -| Resources | `create`, `update` | Specify the container resource requests that the PostgreSQL cluster should use. Follows the [Kubernetes definitions of resource requests](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | -| RootSecretName | `create` | The name of a Kubernetes Secret that contains the credentials for a PostgreSQL superuser that is created when the PostgreSQL cluster is first bootstrapped. For more information, please see `User Secret Specification`.| -| SyncReplication | `create` | If set to `true`, specifies the PostgreSQL cluster to use [synchronous replication]({{< relref "/architecture/high-availability/_index.md#how-the-crunchy-postgresql-operator-uses-pod-anti-affinity#synchronous-replication-guarding-against-transactions-loss" >}}).| -| User | `create` | The name of the PostgreSQL user that is created when the PostgreSQL cluster is first created. | -| UserLabels | `create` | A set of key-value string pairs that are used as a sort of "catch-all" for things that really should be modeled in the CRD. These values do get copied to the actually CR labels. If you want to set up metrics collection or pgBadger, you would specify `"crunchy-postgres-exporter": "true"` and `"crunchy-pgbadger": "true"` here, respectively. However, this structure does need to be set, so just follow whatever is in the example. | -| UserSecretName | `create` | The name of a Kubernetes Secret that contains the credentials for a standard PostgreSQL user that is created when the PostgreSQL cluster is first bootstrapped. For more information, please see `User Secret Specification`.| -| TablespaceMounts | `create`,`update` | Lists any tablespaces that are attached to the PostgreSQL cluster. Tablespaces can be added at a later time by updating the `TablespaceMounts` entry, but they cannot be removed. Stores a map of information, with the key being the name of the tablespace, and the value being a Storage Specification, defined below. | -| TLS | `create` | Defines the attributes for enabling TLS for a PostgreSQL cluster. See TLS Specification below. | -| TLSOnly | `create` | If set to true, requires client connections to use only TLS to connect to the PostgreSQL database. | -| Standby | `create`, `update` | If set to true, indicates that the PostgreSQL cluster is a "standby" cluster, i.e. is in read-only mode entirely. Please see [Kubernetes Multi-Cluster Deployments]({{< relref "/architecture/high-availability/multi-cluster-kubernetes.md" >}}) for more information. | -| Shutdown | `create`, `update` | If set to true, indicates that a PostgreSQL cluster should shutdown. If set to false, indicates that a PostgreSQL cluster should be up and running. | - -##### Storage Specification - -The storage specification is a spec that defines attributes about the storage to -be used for a particular function of a PostgreSQL cluster (e.g. a primary -instance or for the pgBackRest backup repository). The below describes each -attribute and how it works. - -| Attribute | Action | Description | -|-----------|--------|-------------| -| AccessMode| `create` | The name of the Kubernetes Persistent Volume [Access Mode](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes) to use. | -| MatchLabels | `create` | Only used with `StorageType` of `create`, used to match a particular subset of provisioned Persistent Volumes. | -| Name | `create` | Only needed for `PrimaryStorage` in `pgclusters.crunchydata.com`.Used to identify the name of the PostgreSQL cluster. Should match `ClusterName`. | -| Size | `create` | The size of the [Persistent Volume Claim](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#persistentvolumeclaims) (PVC). Must use a Kubernetes resource value, e.g. `20Gi`. | -| StorageClass | `create` | The name of the Kubernetes [StorageClass](https://kubernetes.io/docs/concepts/storage/storage-classes/) to use. | -| StorageType | `create` | Set to `create` if storage is provisioned (e.g. using `hostpath`). Set to `dynamic` if using a dynamic storage provisioner, e.g. via a `StorageClass`. | -| SupplementalGroups | `create` | If provided, a comma-separated list of group IDs to use in case it is needed to interface with a particular storage system. Typically used with NFS or hostpath storage. | - -##### Pod Anti-Affinity Specification - -Sets the [pod anti-affinity]({{< relref "/architecture/high-availability/_index.md#how-the-crunchy-postgresql-operator-uses-pod-anti-affinity" >}}) -for the PostgreSQL cluster and associated deployments. Each attribute can -contain one of the following values: - -- `required` -- `preferred` (which is also the recommended default) -- `disabled` - -For a detailed explanation for how this works. Please see the [high-availability]({{< relref "/architecture/high-availability/_index.md#how-the-crunchy-postgresql-operator-uses-pod-anti-affinity" >}}) -documentation. - -| Attribute | Action | Description | -|-----------|--------|-------------| -| Default | `create` | The default pod anti-affinity to use for all Pods managed in a given PostgreSQL cluster. | -| PgBackRest | `create` | If set to a value that differs from `Default`, specifies the pod anti-affinity to use for just the pgBackRest repository. | -| PgBouncer | `create` | If set to a value that differs from `Default`, specifies the pod anti-affinity to use for just the pgBouncer Pods. | - -##### PostgreSQL Data Source Specification - -This specification is used when one wants to bootstrap the data in a PostgreSQL -cluster from a pgBackRest repository. This can be a pgBackRest repository that -is attached to an active PostgreSQL cluster or is kept around to be used for -spawning new PostgreSQL clusters. - -| Attribute | Action | Description | -|-----------|--------|-------------| -| RestoreFrom | `create` | The name of a PostgreSQL cluster, active or former, that will be used for bootstrapping the data of a new PostgreSQL cluster. | -| RestoreOpts | `create` | Additional pgBackRest [restore options](https://pgbackrest.org/command.html#command-restore) that can be used as part of the bootstrapping operation, for example, point-in-time-recovery options. | - -##### TLS Specification - -The TLS specification makes a reference to the various secrets that are required -to enable TLS in a PostgreSQL cluster. For more information on how these secrets -should be structured, please see [Enabling TLS in a PostgreSQL Cluster]({{< relref "/pgo-client/common-tasks.md#enable-tls" >}}). - -| Attribute | Action | Description | -|-----------|--------|-------------| -| CASecret | `create` | A reference to the name of a Kubernetes Secret that specifies a certificate authority for the PostgreSQL cluster to trust. | -| ReplicationTLSSecret | `create` | A reference to the name of a Kubernetes TLS Secret that contains a keypair for authenticating the replication user. Must be used with `CASecret` and `TLSSecret`. | -| TLSSecret | `create` | A reference to the name of a Kubernetes TLS Secret that contains a keypair that is used for the PostgreSQL instance to identify itself and perform TLS communications with PostgreSQL clients. Must be used with `CASecret`. | - -##### pgBouncer Specification - -The pgBouncer specification defines how a pgBouncer deployment can be deployed -alongside the PostgreSQL cluster. pgBouncer is a PostgreSQL connection pooler -that can also help manage connection state, and is helpful to deploy alongside -a PostgreSQL cluster to help with failover scenarios too. - -| Attribute | Action | Description | -|-----------|--------|-------------| -| Limits | `create`, `update` | Specify the container resource limits that the pgBouncer Pods should use. Follows the [Kubernetes definitions of resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | -| Replicas | `create`, `update` | The number of pgBouncer instances to deploy. Must be set to at least `1` to deploy pgBouncer. Setting to `0` removes an existing pgBouncer deployment for the PostgreSQL cluster. | -| Resources | `create`, `update` | Specify the container resource requests that the pgBouncer Pods should use. Follows the [Kubernetes definitions of resource requests](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | - -##### Annotations Specification - -The `pgcluster.crunchydata.com` specification contains a block that allows for -custom [Annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) -to be applied to the Deployments that are managed by the PostgreSQL Operator, -including: - -- PostgreSQL -- pgBackRest -- pgBouncer - -This also includes the option to apply Annotations globally across the three -different deployment groups. - -| Attribute | Action | Description | -|-----------|--------|-------------| -| Backrest | `create`, `update` | Specify annotations that are only applied to the pgBackRest deployments | -| Global | `create`, `update` | Specify annotations that are applied to the PostgreSQL, pgBackRest, and pgBouncer deployments | -| PgBouncer | `create`, `update` | Specify annotations that are only applied to the pgBouncer deployments | -| Postgres | `create`, `update` | Specify annotations that are only applied to the PostgreSQL deployments | - -### `pgreplicas.crunchydata.com` - -The `pgreplicas.crunchydata.com` Custom Resource Definition contains information -pertaning to the structure of PostgreSQL replicas associated within a PostgreSQL -cluster. All of the attributes only affect the replica when it is created. - -#### Specification (`Spec`) - -| Attribute | Action | Description | -|-----------|--------|-------------| -| ClusterName | `create` | The name of the PostgreSQL cluster, e.g. `hippo`. This is used to group PostgreSQL instances (primary, replicas) together. | -| Name | `create` | The name of this PostgreSQL replica. It should be unique within a `ClusterName`. | -| Namespace | `create` | The Kubernetes Namespace that the PostgreSQL cluster is deployed in. | -| ReplicaStorage | `create` | A specification that gives information about the storage attributes for any replicas in the PostgreSQL cluster. For details, please see the `Storage Specification` section in the `pgclusters.crunchydata.com` description. This will likely be changed in the future based on the nature of the high-availability system, but presently it is still required that you set it. It is recommended you use similar settings to that of `PrimaryStorage`. | -| UserLabels | `create` | A set of key-value string pairs that are used as a sort of "catch-all" for things that really should be modeled in the CRD. These values do get copied to the actually CR labels. If you want to set up metrics collection, you would specify `"crunchy-postgres-exporter": "true"` here. This also allows for node selector pinning using `NodeLabelKey` and `NodeLabelValue`. However, this structure does need to be set, so just follow whatever is in the example. | - ## Custom Resource Workflows ### Create a PostgreSQL Cluster @@ -629,3 +431,201 @@ spec: Save your edits, and in a short period of time, you should see these annotations applied to the managed Deployments. + +## PostgreSQL Operator Custom Resource Definitions + +There are several PostgreSQL Operator Custom Resource Definitions (CRDs) that +are installed in order for the PostgreSQL Operator to successfully function: + +- `pgclusters.crunchydata.com`: Stores information required to manage a +PostgreSQL cluster. This includes things like the cluster name, what storage and +resource classes to use, which version of PostgreSQL to run, information about +how to maintain a high-availability cluster, etc. +- `pgreplicas.crunchydata.com`: Stores information required to manage the +replicas within a PostgreSQL cluster. This includes things like the number of +replicas, what storage and resource classes to use, special affinity rules, etc. +- `pgtasks.crunchydata.com`: A general purpose CRD that accepts a type of task +that is needed to run against a cluster (e.g. take a backup) and tracks the +state of said task through its workflow. +- `pgpolicies.crunchydata.com`: Stores a reference to a SQL file that can be +executed against a PostgreSQL cluster. In the past, this was used to manage RLS +policies on PostgreSQL clusters. + +Below takes an in depth look for what each attribute does in a Custom Resource +Definition, and how they can be used in the creation and update workflow. + +### Glossary + +- `create`: if an attribute is listed as `create`, it means it can affect what +happens when a new Custom Resource is created. +- `update`: if an attribute is listed as `update`, it means it can affect the +Custom Resource, and by extension the objects it manages, when the attribute is +updated. + +### `pgclusters.crunchydata.com` + +The `pgclusters.crunchydata.com` Custom Resource Definition is the fundamental +definition of a PostgreSQL cluster. Most attributes only affect the deployment +of a PostgreSQL cluster at the time the PostgreSQL cluster is created. Some +attributes can be modified during the lifetime of the PostgreSQL cluster and +make changes, as described below. + +#### Specification (`Spec`) + +| Attribute | Action | Description | +|-----------|--------|-------------| +| Annotations | `create`, `update` | Specify Kubernetes [Annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) that can be applied to the different deployments managed by the PostgreSQL Operator (PostgreSQL, pgBackRest, pgBouncer). For more information, please see the "Annotations Specification" below. | +| BackrestConfig | `create` | Optional references to pgBackRest configuration files +| BackrestLimits | `create`, `update` | Specify the container resource limits that the pgBackRest repository should use. Follows the [Kubernetes definitions of resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | +| BackrestResources | `create`, `update` | Specify the container resource requests that the pgBackRest repository should use. Follows the [Kubernetes definitions of resource requests](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | +| BackrestS3Bucket | `create` | An optional parameter that specifies a S3 bucket that pgBackRest should use. | +| BackrestS3Endpoint | `create` | An optional parameter that specifies the S3 endpoint pgBackRest should use. | +| BackrestS3Region | `create` | An optional parameter that specifies a cloud region that pgBackRest should use. | +| BackrestS3URIStyle | `create` | An optional parameter that specifies if pgBackRest should use the `path` or `host` S3 URI style. | +| BackrestS3VerifyTLS | `create` | An optional parameter that specifies if pgBackRest should verify the TLS endpoint. | +| BackrestStorage | `create` | A specification that gives information about the storage attributes for the pgBackRest repository, which stores backups and archives, of the PostgreSQL cluster. For details, please see the `Storage Specification` section below. This is required. | +| CCPImage | `create` | The name of the PostgreSQL container image to use, e.g. `crunchy-postgres-ha` or `crunchy-postgres-ha-gis`. | +| CCPImagePrefix | `create` | If provided, the image prefix (or registry) of the PostgreSQL container image, e.g. `registry.developers.crunchydata.com/crunchydata`. The default is to use the image prefix set in the PostgreSQL Operator configuration. | +| CCPImageTag | `create` | The tag of the PostgreSQL container image to use, e.g. `{{< param centosBase >}}-{{< param postgresVersion >}}-{{< param operatorVersion >}}`. | +| CollectSecretName | `create` | An optional attribute unless `crunchy-postgres-exporter` is specified in the `UserLabels`; contains the name of a Kubernetes Secret that contains the credentials for a PostgreSQL user that is used for metrics collection, and is created when the PostgreSQL cluster is first bootstrapped. For more information, please see `User Secret Specification`.| +| ClusterName | `create` | The name of the PostgreSQL cluster, e.g. `hippo`. This is used to group PostgreSQL instances (primary, replicas) together. | +| CustomConfig | `create` | If specified, references a custom ConfigMap to use when bootstrapping a PostgreSQL cluster. For the shape of this file, please see the section on [Custom Configuration]({{< relref "/advanced/custom-configuration.md" >}}) | +| Database | `create` | The name of a database that the PostgreSQL user can log into after the PostgreSQL cluster is created. | +| ExporterLimits | `create`, `update` | Specify the container resource limits that the `crunchy-postgres-exporter` sidecar uses when it is deployed with a PostgreSQL instance. Follows the [Kubernetes definitions of resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | +| ExporterPort | `create` | If the `"crunchy-postgres-exporter"` label is set in `UserLabels`, then this specifies the port that the metrics sidecar runs on (e.g. `9187`) | +| ExporterResources | `create`, `update` | Specify the container resource requests that the `crunchy-postgres-exporter` sidecar uses when it is deployed with a PostgreSQL instance. Follows the [Kubernetes definitions of resource requests](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | +| Limits | `create`, `update` | Specify the container resource limits that the PostgreSQL cluster should use. Follows the [Kubernetes definitions of resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | +| Name | `create` | The name of the PostgreSQL instance that is the primary. On creation, this should be set to be the same as `ClusterName`. | +| Namespace | `create` | The Kubernetes Namespace that the PostgreSQL cluster is deployed in. | +| PGBadgerPort | `create` | If the `"crunchy-pgbadger"` label is set in `UserLabels`, then this specifies the port that the pgBadger sidecar runs on (e.g. `10000`) | +| PGDataSource | `create` | Used to indicate if a PostgreSQL cluster should bootstrap its data from a pgBackRest repository. This uses the PostgreSQL Data Source Specification, described below. | +| PGOImagePrefix | `create` | If provided, the image prefix (or registry) of any PostgreSQL Operator images that are used for jobs, e.g. `registry.developers.crunchydata.com/crunchydata`. The default is to use the image prefix set in the PostgreSQL Operator configuration. | +| PgBouncer | `create`, `update` | If specified, defines the attributes to use for the pgBouncer connection pooling deployment that can be used in conjunction with this PostgreSQL cluster. Please see the specification defined below. | +| PodAntiAffinity | `create` | A required section. Sets the [pod anti-affinity rules]({{< relref "/architecture/high-availability/_index.md#how-the-crunchy-postgresql-operator-uses-pod-anti-affinity" >}}) for the PostgreSQL cluster and associated deployments. Please see the `Pod Anti-Affinity Specification` section below. | +| Policies | `create` | If provided, a comma-separated list referring to `pgpolicies.crunchydata.com.Spec.Name` that should be run once the PostgreSQL primary is first initialized. | +| Port | `create` | The port that PostgreSQL will run on, e.g. `5432`. | +| PrimaryStorage | `create` | A specification that gives information about the storage attributes for the primary instance in the PostgreSQL cluster. For details, please see the `Storage Specification` section below. This is required. | +| RootSecretName | `create` | The name of a Kubernetes Secret that contains the credentials for a PostgreSQL _replication user_ that is created when the PostgreSQL cluster is first bootstrapped. For more information, please see `User Secret Specification`.| +| ReplicaStorage | `create` | A specification that gives information about the storage attributes for any replicas in the PostgreSQL cluster. For details, please see the `Storage Specification` section below. This will likely be changed in the future based on the nature of the high-availability system, but presently it is still required that you set it. It is recommended you use similar settings to that of `PrimaryStorage`. | +| Replicas | `create` | The number of replicas to create after a PostgreSQL primary is first initialized. This only works on create; to scale a cluster after it is initialized, please use the [`pgo scale`]({{< relref "/pgo-client/reference/pgo_scale.md" >}}) command. | +| Resources | `create`, `update` | Specify the container resource requests that the PostgreSQL cluster should use. Follows the [Kubernetes definitions of resource requests](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | +| RootSecretName | `create` | The name of a Kubernetes Secret that contains the credentials for a PostgreSQL superuser that is created when the PostgreSQL cluster is first bootstrapped. For more information, please see `User Secret Specification`.| +| SyncReplication | `create` | If set to `true`, specifies the PostgreSQL cluster to use [synchronous replication]({{< relref "/architecture/high-availability/_index.md#how-the-crunchy-postgresql-operator-uses-pod-anti-affinity#synchronous-replication-guarding-against-transactions-loss" >}}).| +| User | `create` | The name of the PostgreSQL user that is created when the PostgreSQL cluster is first created. | +| UserLabels | `create` | A set of key-value string pairs that are used as a sort of "catch-all" for things that really should be modeled in the CRD. These values do get copied to the actually CR labels. If you want to set up metrics collection or pgBadger, you would specify `"crunchy-postgres-exporter": "true"` and `"crunchy-pgbadger": "true"` here, respectively. However, this structure does need to be set, so just follow whatever is in the example. | +| UserSecretName | `create` | The name of a Kubernetes Secret that contains the credentials for a standard PostgreSQL user that is created when the PostgreSQL cluster is first bootstrapped. For more information, please see `User Secret Specification`.| +| TablespaceMounts | `create`,`update` | Lists any tablespaces that are attached to the PostgreSQL cluster. Tablespaces can be added at a later time by updating the `TablespaceMounts` entry, but they cannot be removed. Stores a map of information, with the key being the name of the tablespace, and the value being a Storage Specification, defined below. | +| TLS | `create` | Defines the attributes for enabling TLS for a PostgreSQL cluster. See TLS Specification below. | +| TLSOnly | `create` | If set to true, requires client connections to use only TLS to connect to the PostgreSQL database. | +| Standby | `create`, `update` | If set to true, indicates that the PostgreSQL cluster is a "standby" cluster, i.e. is in read-only mode entirely. Please see [Kubernetes Multi-Cluster Deployments]({{< relref "/architecture/high-availability/multi-cluster-kubernetes.md" >}}) for more information. | +| Shutdown | `create`, `update` | If set to true, indicates that a PostgreSQL cluster should shutdown. If set to false, indicates that a PostgreSQL cluster should be up and running. | + +##### Storage Specification + +The storage specification is a spec that defines attributes about the storage to +be used for a particular function of a PostgreSQL cluster (e.g. a primary +instance or for the pgBackRest backup repository). The below describes each +attribute and how it works. + +| Attribute | Action | Description | +|-----------|--------|-------------| +| AccessMode| `create` | The name of the Kubernetes Persistent Volume [Access Mode](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes) to use. | +| MatchLabels | `create` | Only used with `StorageType` of `create`, used to match a particular subset of provisioned Persistent Volumes. | +| Name | `create` | Only needed for `PrimaryStorage` in `pgclusters.crunchydata.com`.Used to identify the name of the PostgreSQL cluster. Should match `ClusterName`. | +| Size | `create` | The size of the [Persistent Volume Claim](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#persistentvolumeclaims) (PVC). Must use a Kubernetes resource value, e.g. `20Gi`. | +| StorageClass | `create` | The name of the Kubernetes [StorageClass](https://kubernetes.io/docs/concepts/storage/storage-classes/) to use. | +| StorageType | `create` | Set to `create` if storage is provisioned (e.g. using `hostpath`). Set to `dynamic` if using a dynamic storage provisioner, e.g. via a `StorageClass`. | +| SupplementalGroups | `create` | If provided, a comma-separated list of group IDs to use in case it is needed to interface with a particular storage system. Typically used with NFS or hostpath storage. | + +##### Pod Anti-Affinity Specification + +Sets the [pod anti-affinity]({{< relref "/architecture/high-availability/_index.md#how-the-crunchy-postgresql-operator-uses-pod-anti-affinity" >}}) +for the PostgreSQL cluster and associated deployments. Each attribute can +contain one of the following values: + +- `required` +- `preferred` (which is also the recommended default) +- `disabled` + +For a detailed explanation for how this works. Please see the [high-availability]({{< relref "/architecture/high-availability/_index.md#how-the-crunchy-postgresql-operator-uses-pod-anti-affinity" >}}) +documentation. + +| Attribute | Action | Description | +|-----------|--------|-------------| +| Default | `create` | The default pod anti-affinity to use for all Pods managed in a given PostgreSQL cluster. | +| PgBackRest | `create` | If set to a value that differs from `Default`, specifies the pod anti-affinity to use for just the pgBackRest repository. | +| PgBouncer | `create` | If set to a value that differs from `Default`, specifies the pod anti-affinity to use for just the pgBouncer Pods. | + +##### PostgreSQL Data Source Specification + +This specification is used when one wants to bootstrap the data in a PostgreSQL +cluster from a pgBackRest repository. This can be a pgBackRest repository that +is attached to an active PostgreSQL cluster or is kept around to be used for +spawning new PostgreSQL clusters. + +| Attribute | Action | Description | +|-----------|--------|-------------| +| RestoreFrom | `create` | The name of a PostgreSQL cluster, active or former, that will be used for bootstrapping the data of a new PostgreSQL cluster. | +| RestoreOpts | `create` | Additional pgBackRest [restore options](https://pgbackrest.org/command.html#command-restore) that can be used as part of the bootstrapping operation, for example, point-in-time-recovery options. | + +##### TLS Specification + +The TLS specification makes a reference to the various secrets that are required +to enable TLS in a PostgreSQL cluster. For more information on how these secrets +should be structured, please see [Enabling TLS in a PostgreSQL Cluster]({{< relref "/pgo-client/common-tasks.md#enable-tls" >}}). + +| Attribute | Action | Description | +|-----------|--------|-------------| +| CASecret | `create` | A reference to the name of a Kubernetes Secret that specifies a certificate authority for the PostgreSQL cluster to trust. | +| ReplicationTLSSecret | `create` | A reference to the name of a Kubernetes TLS Secret that contains a keypair for authenticating the replication user. Must be used with `CASecret` and `TLSSecret`. | +| TLSSecret | `create` | A reference to the name of a Kubernetes TLS Secret that contains a keypair that is used for the PostgreSQL instance to identify itself and perform TLS communications with PostgreSQL clients. Must be used with `CASecret`. | + +##### pgBouncer Specification + +The pgBouncer specification defines how a pgBouncer deployment can be deployed +alongside the PostgreSQL cluster. pgBouncer is a PostgreSQL connection pooler +that can also help manage connection state, and is helpful to deploy alongside +a PostgreSQL cluster to help with failover scenarios too. + +| Attribute | Action | Description | +|-----------|--------|-------------| +| Limits | `create`, `update` | Specify the container resource limits that the pgBouncer Pods should use. Follows the [Kubernetes definitions of resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | +| Replicas | `create`, `update` | The number of pgBouncer instances to deploy. Must be set to at least `1` to deploy pgBouncer. Setting to `0` removes an existing pgBouncer deployment for the PostgreSQL cluster. | +| Resources | `create`, `update` | Specify the container resource requests that the pgBouncer Pods should use. Follows the [Kubernetes definitions of resource requests](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | + +##### Annotations Specification + +The `pgcluster.crunchydata.com` specification contains a block that allows for +custom [Annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) +to be applied to the Deployments that are managed by the PostgreSQL Operator, +including: + +- PostgreSQL +- pgBackRest +- pgBouncer + +This also includes the option to apply Annotations globally across the three +different deployment groups. + +| Attribute | Action | Description | +|-----------|--------|-------------| +| Backrest | `create`, `update` | Specify annotations that are only applied to the pgBackRest deployments | +| Global | `create`, `update` | Specify annotations that are applied to the PostgreSQL, pgBackRest, and pgBouncer deployments | +| PgBouncer | `create`, `update` | Specify annotations that are only applied to the pgBouncer deployments | +| Postgres | `create`, `update` | Specify annotations that are only applied to the PostgreSQL deployments | + +### `pgreplicas.crunchydata.com` + +The `pgreplicas.crunchydata.com` Custom Resource Definition contains information +pertaning to the structure of PostgreSQL replicas associated within a PostgreSQL +cluster. All of the attributes only affect the replica when it is created. + +#### Specification (`Spec`) + +| Attribute | Action | Description | +|-----------|--------|-------------| +| ClusterName | `create` | The name of the PostgreSQL cluster, e.g. `hippo`. This is used to group PostgreSQL instances (primary, replicas) together. | +| Name | `create` | The name of this PostgreSQL replica. It should be unique within a `ClusterName`. | +| Namespace | `create` | The Kubernetes Namespace that the PostgreSQL cluster is deployed in. | +| ReplicaStorage | `create` | A specification that gives information about the storage attributes for any replicas in the PostgreSQL cluster. For details, please see the `Storage Specification` section in the `pgclusters.crunchydata.com` description. This will likely be changed in the future based on the nature of the high-availability system, but presently it is still required that you set it. It is recommended you use similar settings to that of `PrimaryStorage`. | +| UserLabels | `create` | A set of key-value string pairs that are used as a sort of "catch-all" for things that really should be modeled in the CRD. These values do get copied to the actually CR labels. If you want to set up metrics collection, you would specify `"crunchy-postgres-exporter": "true"` here. This also allows for node selector pinning using `NodeLabelKey` and `NodeLabelValue`. However, this structure does need to be set, so just follow whatever is in the example. | From 050e063dc67cc4e4b653a5aaed97369c85298601 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 27 Oct 2020 15:24:17 -0400 Subject: [PATCH 003/276] Add custom resource example for pgBackRest repo in S3 This examples shows how one can create a new PostgreSQL cluster where the pgBackRest backups and archives exist in a S3 repository via creating a custom resource. --- docs/content/custom-resources/_index.md | 192 ++++++++++++++++++++++++ 1 file changed, 192 insertions(+) diff --git a/docs/content/custom-resources/_index.md b/docs/content/custom-resources/_index.md index 7e024900f4..f4384e311e 100644 --- a/docs/content/custom-resources/_index.md +++ b/docs/content/custom-resources/_index.md @@ -212,6 +212,198 @@ EOF kubectl apply -f "${pgo_cluster_name}-pgcluster.yaml" ``` +### Create a PostgreSQL Cluster With Backups in S3 + +A frequent use case is to create a PostgreSQL cluster with S3 or a S3-like +storage system for storing backups. This requires adding a Secret that contains +the S3 key and key secret for your account, and adding some additional +information into the custom resource. + +#### Step 1: Create the pgBackRest S3 Secrets + +As mentioned above, it is necessary to create a Secret containing the S3 key and +key secret that will allow a user to create backups in S3. + +The below code will help you set up this Secret. + +``` +# this variable is the name of the cluster being created +pgo_cluster_name=hippo +# this variable is the namespace the cluster is being deployed into +cluster_namespace=pgo +# the following variables are your S3 key and key secret +backrest_s3_key=yours3key +backrest_s3_key_secret=yours3keysecret + +kubectl -n "${cluster_namespace}" create secret generic "${pgo_cluster_name}-backrest-repo-config" \ + --from-literal="aws-s3-key=${backrest_s3_key}" \ + --from-literal="aws-s3-key-secret=${backrest_s3_key_secret}" + +unset backrest_s3_key +unset backrest_s3_key_secret +``` + +#### Step 2: Creating the PostgreSQL User Secrets + +Similar to the basic create cluster example, there are a minimum of three +PostgreSQL user accounts that you must create in order to bootstrap a PostgreSQL +cluster. These are: + +- A PostgreSQL superuser +- A replication user +- A standard PostgreSQL user + +The below code will help you set up these Secrets. + +``` +# this variable is the name of the cluster being created +pgo_cluster_name=hippo +# this variable is the namespace the cluster is being deployed into +cluster_namespace=pgo + +# this is the superuser secret +kubectl create secret generic -n "${cluster_namespace}" "${pgo_cluster_name}-postgres-secret" \ + --from-literal=username=postgres \ + --from-literal=password=Supersecurepassword* + +# this is the replication user secret +kubectl create secret generic -n "${cluster_namespace}" "${pgo_cluster_name}-primaryuser-secret" \ + --from-literal=username=primaryuser \ + --from-literal=password=Anothersecurepassword* + +# this is the standard user secret +kubectl create secret generic -n "${cluster_namespace}" "${pgo_cluster_name}-hippo-secret" \ + --from-literal=username=hippo \ + --from-literal=password=Moresecurepassword* + + +kubectl label secrets -n "${cluster_namespace}" "${pgo_cluster_name}-postgres-secret" "pg-cluster=${pgo_cluster_name}" +kubectl label secrets -n "${cluster_namespace}" "${pgo_cluster_name}-primaryuser-secret" "pg-cluster=${pgo_cluster_name}" +kubectl label secrets -n "${cluster_namespace}" "${pgo_cluster_name}-hippo-secret" "pg-cluster=${pgo_cluster_name}" +``` + +#### Step 3: Create the PostgreSQL Cluster + +With the Secrets in place. It is now time to create the PostgreSQL cluster. + +The below manifest references the Secrets created in the previous step to add a +custom resource to the `pgclusters.crunchydata.com` custom resource definition. +There are some additions in this example specifically for storing backups in S3. + +``` +# this variable is the name of the cluster being created +export pgo_cluster_name=hippo +# this variable is the namespace the cluster is being deployed into +export cluster_namespace=pgo +# the following variables store the information for your S3 cluster. You may +# need to adjust them for your actual settings +export backrest_s3_bucket=your-bucket +export backrest_s3_endpoint=s3.region-name.amazonaws.com +export backrest_s3_region=region-name + +cat <<-EOF > "${pgo_cluster_name}-pgcluster.yaml" +apiVersion: crunchydata.com/v1 +kind: Pgcluster +metadata: + annotations: + current-primary: ${pgo_cluster_name} + labels: + autofail: "true" + backrest-storage-type: "s3" + crunchy-pgbadger: "false" + crunchy-pgha-scope: ${pgo_cluster_name} + crunchy-postgres-exporter: "false" + deployment-name: ${pgo_cluster_name} + name: ${pgo_cluster_name} + pg-cluster: ${pgo_cluster_name} + pg-pod-anti-affinity: "" + pgo-backrest: "true" + pgo-version: {{< param operatorVersion >}} + pgouser: admin + name: ${pgo_cluster_name} + namespace: ${cluster_namespace} +spec: + BackrestStorage: + accessmode: ReadWriteMany + matchLabels: "" + name: "" + size: 1G + storageclass: "" + storagetype: dynamic + supplementalgroups: "" + PrimaryStorage: + accessmode: ReadWriteMany + matchLabels: "" + name: ${pgo_cluster_name} + size: 1G + storageclass: "" + storagetype: dynamic + supplementalgroups: "" + ReplicaStorage: + accessmode: ReadWriteMany + matchLabels: "" + name: "" + size: 1G + storageclass: "" + storagetype: dynamic + supplementalgroups: "" + annotations: + backrestLimits: {} + backrestRepoPath: "" + backrestResources: + memory: 48Mi + backrestS3Bucket: ${backrest_s3_bucket} + backrestS3Endpoint: ${backrest_s3_endpoint} + backrestS3Region: ${backrest_s3_region} + backrestS3URIStyle: "" + backrestS3VerifyTLS: "" + ccpimage: crunchy-postgres-ha + ccpimageprefix: registry.developers.crunchydata.com/crunchydata + ccpimagetag: {{< param centosBase >}}-{{< param postgresVersion >}}-{{< param operatorVersion >}} + clustername: ${pgo_cluster_name} + customconfig: "" + database: ${pgo_cluster_name} + exporterport: "9187" + limits: {} + name: ${pgo_cluster_name} + namespace: ${cluster_namespace} + pgBouncer: + limits: {} + replicas: 0 + pgDataSource: + restoreFrom: "" + restoreOpts: "" + pgbadgerport: "10000" + pgoimageprefix: registry.developers.crunchydata.com/crunchydata + podAntiAffinity: + default: preferred + pgBackRest: preferred + pgBouncer: preferred + policies: "" + port: "5432" + primarysecretname: ${pgo_cluster_name}-primaryuser-secret + replicas: "0" + rootsecretname: ${pgo_cluster_name}-postgres-secret + shutdown: false + standby: false + tablespaceMounts: {} + tls: + caSecret: "" + replicationTLSSecret: "" + tlsSecret: "" + tlsOnly: false + user: hippo + userlabels: + backrest-storage-type: "s3" + crunchy-postgres-exporter: "false" + pg-pod-anti-affinity: "" + pgo-version: {{< param operatorVersion >}} + usersecretname: ${pgo_cluster_name}-hippo-secret +EOF + +kubectl apply -f "${pgo_cluster_name}-pgcluster.yaml" +``` + ### Modify a Cluster There following modification operations are supported on the From 5c832c5ecd77e9b7bff928fff929cda78761d193 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 27 Oct 2020 20:40:12 -0400 Subject: [PATCH 004/276] Create "pgo-config" ConfigMap if not provided The Operator (and associated containers) will create the pgo-config ConfigMap from the stored default configuration if the pgo-config ConfigMap is not present. This allows for the editing of "default configuration" after the Operator is deployed, and allows for the removal of the pgo-config creation step from several of the installation methods. Issue: [ch9451] --- docs/content/Configuration/configuration.md | 6 +- .../installation/other/operator-hub.md | 14 -- installers/olm/description.openshift.md | 14 -- installers/olm/description.upstream.md | 14 -- internal/config/pgoconfig.go | 178 +++++++++++------- 5 files changed, 114 insertions(+), 112 deletions(-) diff --git a/docs/content/Configuration/configuration.md b/docs/content/Configuration/configuration.md index e85823a865..e6bae33be9 100644 --- a/docs/content/Configuration/configuration.md +++ b/docs/content/Configuration/configuration.md @@ -16,9 +16,9 @@ The configuration files used by the Operator are found in 2 places: * the pgo-config ConfigMap in the namespace the Operator is running in * or, a copy of the configuration files are also included by default into the Operator container images themselves to support a very simplistic deployment of the Operator -If the pgo-config ConfigMap is not found by the Operator, it will use -the configuration files that are included in the Operator container -images. +If the `pgo-config` ConfigMap is not found by the Operator, it will create a +`pgo-config` ConfigMap using the configuration files that are included in the +Operator container. ## conf/postgres-operator/pgo.yaml The *pgo.yaml* file sets many different Operator configuration settings and is described in the [pgo.yaml configuration]({{< ref "pgo-yaml-configuration.md" >}}) documentation section. diff --git a/docs/content/installation/other/operator-hub.md b/docs/content/installation/other/operator-hub.md index 9b077ef073..b610ee2664 100644 --- a/docs/content/installation/other/operator-hub.md +++ b/docs/content/installation/other/operator-hub.md @@ -43,19 +43,6 @@ git clone -b v{{< param operatorVersion >}} https://github.com/CrunchyData/postg cd postgres-operator ``` -### PostgreSQL Operator Configuration - -Edit `conf/postgres-operator/pgo.yaml` to configure the deployment. Look over all of the options and make any -changes necessary for your environment. A full description of each option is available in the -[`pgo.yaml` configuration guide]({{< relref "configuration/pgo-yaml-configuration.md" >}}). - -When the file is ready, upload the entire directory to the `pgo-config` ConfigMap. - -``` -kubectl -n "$PGO_OPERATOR_NAMESPACE" create configmap pgo-config \ - --from-file=./conf/postgres-operator -``` - ### Secrets Configure pgBackRest for your environment. If you do not plan to use AWS S3 to store backups, you can omit @@ -152,4 +139,3 @@ pgo version # pgo client version {{< param operatorVersion >}} # pgo-apiserver version {{< param operatorVersion >}} ``` - diff --git a/installers/olm/description.openshift.md b/installers/olm/description.openshift.md index ad31cbe1e5..e4eb3c0831 100644 --- a/installers/olm/description.openshift.md +++ b/installers/olm/description.openshift.md @@ -63,20 +63,6 @@ edit `conf/postgres-operator/pgo.yaml` and set `DisableFSGroup` to `true`. [Security Context Constraint]: https://docs.openshift.com/container-platform/latest/authentication/managing-security-context-constraints.html -### PostgreSQL Operator Configuration - -Edit `conf/postgres-operator/pgo.yaml` to configure the deployment. Look over all of the options and make any -changes necessary for your environment. A [full description of each option][pgo-yaml-reference] is available in the documentation. - -[pgo-yaml-reference]: https://access.crunchydata.com/documentation/postgres-operator/${PGO_VERSION}/configuration/pgo-yaml-configuration/ - -When the file is ready, upload the entire directory to the `pgo-config` ConfigMap. - -``` -oc -n "$PGO_OPERATOR_NAMESPACE" create configmap pgo-config \ - --from-file=./conf/postgres-operator -``` - ### Secrets Configure pgBackRest for your environment. If you do not plan to use AWS S3 to store backups, you can omit diff --git a/installers/olm/description.upstream.md b/installers/olm/description.upstream.md index 8838098032..1e192fa9c2 100644 --- a/installers/olm/description.upstream.md +++ b/installers/olm/description.upstream.md @@ -56,20 +56,6 @@ git clone -b v${PGO_VERSION} https://github.com/CrunchyData/postgres-operator.gi cd postgres-operator ``` -### PostgreSQL Operator Configuration - -Edit `conf/postgres-operator/pgo.yaml` to configure the deployment. Look over all of the options and make any -changes necessary for your environment. A [full description of each option][pgo-yaml-reference] is available in the documentation. - -[pgo-yaml-reference]: https://access.crunchydata.com/documentation/postgres-operator/${PGO_VERSION}/configuration/pgo-yaml-configuration/ - -When the file is ready, upload the entire directory to the `pgo-config` ConfigMap. - -``` -kubectl -n "$PGO_OPERATOR_NAMESPACE" create configmap pgo-config \ - --from-file=./conf/postgres-operator -``` - ### Secrets Configure pgBackRest for your environment. If you do not plan to use AWS S3 to store backups, you can omit diff --git a/internal/config/pgoconfig.go b/internal/config/pgoconfig.go index b867aa8d93..ddb04cbf00 100644 --- a/internal/config/pgoconfig.go +++ b/internal/config/pgoconfig.go @@ -21,6 +21,7 @@ import ( "fmt" "io/ioutil" "os" + "path/filepath" "strconv" "strings" "text/template" @@ -29,6 +30,7 @@ import ( log "github.com/sirupsen/logrus" v1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/validation" @@ -37,8 +39,7 @@ import ( ) const CustomConfigMapName = "pgo-config" -const DefaultConfigsPath = "/default-pgo-config/" -const CustomConfigsPath = "/pgo-config/" +const defaultConfigPath = "/default-pgo-config/" var PgoDefaultServiceAccountTemplate *template.Template @@ -513,29 +514,17 @@ func (c *PgoConfig) GetStorageSpec(name string) (crv1.PgStorageSpec, error) { func (c *PgoConfig) GetConfig(clientset kubernetes.Interface, namespace string) error { - cMap, rootPath := getRootPath(clientset, namespace) - - var yamlFile []byte - var err error + cMap, err := initialize(clientset, namespace) //get the pgo.yaml config file - if cMap != nil { - str := cMap.Data[CONFIG_PATH] - if str == "" { - errMsg := fmt.Sprintf("could not get %s from ConfigMap", CONFIG_PATH) - return errors.New(errMsg) - } - yamlFile = []byte(str) - } else { - yamlFile, err = ioutil.ReadFile(rootPath + CONFIG_PATH) - if err != nil { - log.Errorf("yamlFile.Get err #%v ", err) - return err - } + str := cMap.Data[CONFIG_PATH] + if str == "" { + return fmt.Errorf("could not get %s from ConfigMap", CONFIG_PATH) } - err = yaml.Unmarshal(yamlFile, c) - if err != nil { + yamlFile := []byte(str) + + if err := yaml.Unmarshal(yamlFile, c); err != nil { log.Errorf("Unmarshal: %v", err) return err } @@ -549,178 +538,178 @@ func (c *PgoConfig) GetConfig(clientset kubernetes.Interface, namespace string) c.CheckEnv() //load up all the templates - PgoDefaultServiceAccountTemplate, err = c.LoadTemplate(cMap, rootPath, PGODefaultServiceAccountPath) + PgoDefaultServiceAccountTemplate, err = c.LoadTemplate(cMap, PGODefaultServiceAccountPath) if err != nil { return err } - PgoBackrestServiceAccountTemplate, err = c.LoadTemplate(cMap, rootPath, PGOBackrestServiceAccountPath) + PgoBackrestServiceAccountTemplate, err = c.LoadTemplate(cMap, PGOBackrestServiceAccountPath) if err != nil { return err } - PgoTargetServiceAccountTemplate, err = c.LoadTemplate(cMap, rootPath, PGOTargetServiceAccountPath) + PgoTargetServiceAccountTemplate, err = c.LoadTemplate(cMap, PGOTargetServiceAccountPath) if err != nil { return err } - PgoTargetRoleBindingTemplate, err = c.LoadTemplate(cMap, rootPath, PGOTargetRoleBindingPath) + PgoTargetRoleBindingTemplate, err = c.LoadTemplate(cMap, PGOTargetRoleBindingPath) if err != nil { return err } - PgoBackrestRoleTemplate, err = c.LoadTemplate(cMap, rootPath, PGOBackrestRolePath) + PgoBackrestRoleTemplate, err = c.LoadTemplate(cMap, PGOBackrestRolePath) if err != nil { return err } - PgoBackrestRoleBindingTemplate, err = c.LoadTemplate(cMap, rootPath, PGOBackrestRoleBindingPath) + PgoBackrestRoleBindingTemplate, err = c.LoadTemplate(cMap, PGOBackrestRoleBindingPath) if err != nil { return err } - PgoTargetRoleTemplate, err = c.LoadTemplate(cMap, rootPath, PGOTargetRolePath) + PgoTargetRoleTemplate, err = c.LoadTemplate(cMap, PGOTargetRolePath) if err != nil { return err } - PgoPgServiceAccountTemplate, err = c.LoadTemplate(cMap, rootPath, PGOPgServiceAccountPath) + PgoPgServiceAccountTemplate, err = c.LoadTemplate(cMap, PGOPgServiceAccountPath) if err != nil { return err } - PgoPgRoleTemplate, err = c.LoadTemplate(cMap, rootPath, PGOPgRolePath) + PgoPgRoleTemplate, err = c.LoadTemplate(cMap, PGOPgRolePath) if err != nil { return err } - PgoPgRoleBindingTemplate, err = c.LoadTemplate(cMap, rootPath, PGOPgRoleBindingPath) + PgoPgRoleBindingTemplate, err = c.LoadTemplate(cMap, PGOPgRoleBindingPath) if err != nil { return err } - PVCTemplate, err = c.LoadTemplate(cMap, rootPath, pvcPath) + PVCTemplate, err = c.LoadTemplate(cMap, pvcPath) if err != nil { return err } - PolicyJobTemplate, err = c.LoadTemplate(cMap, rootPath, policyJobTemplatePath) + PolicyJobTemplate, err = c.LoadTemplate(cMap, policyJobTemplatePath) if err != nil { return err } - ContainerResourcesTemplate, err = c.LoadTemplate(cMap, rootPath, containerResourcesTemplatePath) + ContainerResourcesTemplate, err = c.LoadTemplate(cMap, containerResourcesTemplatePath) if err != nil { return err } - PgoBackrestRepoServiceTemplate, err = c.LoadTemplate(cMap, rootPath, pgoBackrestRepoServiceTemplatePath) + PgoBackrestRepoServiceTemplate, err = c.LoadTemplate(cMap, pgoBackrestRepoServiceTemplatePath) if err != nil { return err } - PgoBackrestRepoTemplate, err = c.LoadTemplate(cMap, rootPath, pgoBackrestRepoTemplatePath) + PgoBackrestRepoTemplate, err = c.LoadTemplate(cMap, pgoBackrestRepoTemplatePath) if err != nil { return err } - PgmonitorEnvVarsTemplate, err = c.LoadTemplate(cMap, rootPath, pgmonitorEnvVarsPath) + PgmonitorEnvVarsTemplate, err = c.LoadTemplate(cMap, pgmonitorEnvVarsPath) if err != nil { return err } - PgbackrestEnvVarsTemplate, err = c.LoadTemplate(cMap, rootPath, pgbackrestEnvVarsPath) + PgbackrestEnvVarsTemplate, err = c.LoadTemplate(cMap, pgbackrestEnvVarsPath) if err != nil { return err } - PgbackrestS3EnvVarsTemplate, err = c.LoadTemplate(cMap, rootPath, pgbackrestS3EnvVarsPath) + PgbackrestS3EnvVarsTemplate, err = c.LoadTemplate(cMap, pgbackrestS3EnvVarsPath) if err != nil { return err } - PgAdminTemplate, err = c.LoadTemplate(cMap, rootPath, pgAdminTemplatePath) + PgAdminTemplate, err = c.LoadTemplate(cMap, pgAdminTemplatePath) if err != nil { return err } - PgAdminServiceTemplate, err = c.LoadTemplate(cMap, rootPath, pgAdminServiceTemplatePath) + PgAdminServiceTemplate, err = c.LoadTemplate(cMap, pgAdminServiceTemplatePath) if err != nil { return err } - PgbouncerTemplate, err = c.LoadTemplate(cMap, rootPath, pgbouncerTemplatePath) + PgbouncerTemplate, err = c.LoadTemplate(cMap, pgbouncerTemplatePath) if err != nil { return err } - PgbouncerConfTemplate, err = c.LoadTemplate(cMap, rootPath, pgbouncerConfTemplatePath) + PgbouncerConfTemplate, err = c.LoadTemplate(cMap, pgbouncerConfTemplatePath) if err != nil { return err } - PgbouncerUsersTemplate, err = c.LoadTemplate(cMap, rootPath, pgbouncerUsersTemplatePath) + PgbouncerUsersTemplate, err = c.LoadTemplate(cMap, pgbouncerUsersTemplatePath) if err != nil { return err } - PgbouncerHBATemplate, err = c.LoadTemplate(cMap, rootPath, pgbouncerHBATemplatePath) + PgbouncerHBATemplate, err = c.LoadTemplate(cMap, pgbouncerHBATemplatePath) if err != nil { return err } - ServiceTemplate, err = c.LoadTemplate(cMap, rootPath, serviceTemplatePath) + ServiceTemplate, err = c.LoadTemplate(cMap, serviceTemplatePath) if err != nil { return err } - RmdatajobTemplate, err = c.LoadTemplate(cMap, rootPath, rmdatajobPath) + RmdatajobTemplate, err = c.LoadTemplate(cMap, rmdatajobPath) if err != nil { return err } - BackrestjobTemplate, err = c.LoadTemplate(cMap, rootPath, backrestjobPath) + BackrestjobTemplate, err = c.LoadTemplate(cMap, backrestjobPath) if err != nil { return err } - PgDumpBackupJobTemplate, err = c.LoadTemplate(cMap, rootPath, pgDumpBackupJobPath) + PgDumpBackupJobTemplate, err = c.LoadTemplate(cMap, pgDumpBackupJobPath) if err != nil { return err } - PgRestoreJobTemplate, err = c.LoadTemplate(cMap, rootPath, pgRestoreJobPath) + PgRestoreJobTemplate, err = c.LoadTemplate(cMap, pgRestoreJobPath) if err != nil { return err } - PVCMatchLabelsTemplate, err = c.LoadTemplate(cMap, rootPath, pvcMatchLabelsPath) + PVCMatchLabelsTemplate, err = c.LoadTemplate(cMap, pvcMatchLabelsPath) if err != nil { return err } - PVCStorageClassTemplate, err = c.LoadTemplate(cMap, rootPath, pvcSCPath) + PVCStorageClassTemplate, err = c.LoadTemplate(cMap, pvcSCPath) if err != nil { return err } - AffinityTemplate, err = c.LoadTemplate(cMap, rootPath, affinityTemplatePath) + AffinityTemplate, err = c.LoadTemplate(cMap, affinityTemplatePath) if err != nil { return err } - PodAntiAffinityTemplate, err = c.LoadTemplate(cMap, rootPath, podAntiAffinityTemplatePath) + PodAntiAffinityTemplate, err = c.LoadTemplate(cMap, podAntiAffinityTemplatePath) if err != nil { return err } - ExporterTemplate, err = c.LoadTemplate(cMap, rootPath, exporterTemplatePath) + ExporterTemplate, err = c.LoadTemplate(cMap, exporterTemplatePath) if err != nil { return err } - BadgerTemplate, err = c.LoadTemplate(cMap, rootPath, badgerTemplatePath) + BadgerTemplate, err = c.LoadTemplate(cMap, badgerTemplatePath) if err != nil { return err } - DeploymentTemplate, err = c.LoadTemplate(cMap, rootPath, deploymentTemplatePath) + DeploymentTemplate, err = c.LoadTemplate(cMap, deploymentTemplatePath) if err != nil { return err } - BootstrapTemplate, err = c.LoadTemplate(cMap, rootPath, bootstrapTemplatePath) + BootstrapTemplate, err = c.LoadTemplate(cMap, bootstrapTemplatePath) if err != nil { return err } @@ -728,20 +717,75 @@ func (c *PgoConfig) GetConfig(clientset kubernetes.Interface, namespace string) return nil } -func getRootPath(clientset kubernetes.Interface, namespace string) (*v1.ConfigMap, string) { +// getOperatorConfigMap returns the config map that contains all of the +// configuration for the Operator +func getOperatorConfigMap(clientset kubernetes.Interface, namespace string) (*v1.ConfigMap, error) { + ctx := context.TODO() + + return clientset.CoreV1().ConfigMaps(namespace).Get(ctx, CustomConfigMapName, metav1.GetOptions{}) +} + +// initialize attemps to get the configuration ConfigMap based on a name. +// If the ConfigMap does not exist, a ConfigMap is created from the default +// configuration path +func initialize(clientset kubernetes.Interface, namespace string) (*v1.ConfigMap, error) { ctx := context.TODO() - cMap, err := clientset.CoreV1().ConfigMaps(namespace).Get(ctx, CustomConfigMapName, metav1.GetOptions{}) - if err == nil { - log.Infof("Config: %s ConfigMap found, using config files from the configmap", CustomConfigMapName) - return cMap, "" + + // if the ConfigMap exists, exit + if cm, err := getOperatorConfigMap(clientset, namespace); err == nil { + log.Infof("Config: %q ConfigMap found, using config files from the configmap", CustomConfigMapName) + return cm, nil + } + + // otherwise, create a ConfigMap + log.Infof("Config: %q ConfigMap NOT found, creating ConfigMap from files from %q", CustomConfigMapName, defaultConfigPath) + + cm := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: CustomConfigMapName, + }, + Data: map[string]string{}, + } + + // get all of the file names that are in the default configuration directory + if err := filepath.Walk(defaultConfigPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + // skip if a directory + if info.IsDir() { + return nil + } + + // get all of the contents of a default configuration and load it into + // a ConfigMap + if contents, err := ioutil.ReadFile(path); err != nil { + return err + } else { + cm.Data[info.Name()] = string(contents) + } + + return nil + }); err != nil { + return nil, err + } + + // create the ConfigMap. If the error is that the ConfigMap was already + // created, then grab the new ConfigMap + if _, err := clientset.CoreV1().ConfigMaps(namespace).Create(ctx, cm, metav1.CreateOptions{}); err != nil { + if kerrors.IsAlreadyExists(err) { + return getOperatorConfigMap(clientset, namespace) + } + + return nil, err } - log.Infof("Config: %s ConfigMap NOT found, using default baked-in config files from %s", CustomConfigMapName, DefaultConfigsPath) - return nil, DefaultConfigsPath + return cm, nil } // LoadTemplate will load a JSON template from a path -func (c *PgoConfig) LoadTemplate(cMap *v1.ConfigMap, rootPath, path string) (*template.Template, error) { +func (c *PgoConfig) LoadTemplate(cMap *v1.ConfigMap, path string) (*template.Template, error) { var value string var err error @@ -771,7 +815,7 @@ func (c *PgoConfig) LoadTemplate(cMap *v1.ConfigMap, rootPath, path string) (*te func (c *PgoConfig) DefaultTemplate(path string) (string, error) { // set the lookup value for the file path based on the default configuration // path and the template file requested to be loaded - fullPath := DefaultConfigsPath + path + fullPath := defaultConfigPath + path log.Debugf("No entry in cmap loading default path [%s]", fullPath) From 05191d4232b753683ab5380097185b33c9cd6711 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 9 Nov 2020 15:01:08 -0500 Subject: [PATCH 005/276] Remove pgBackRest Secret creation from Helm chart example This is no longer needed, as the Operator will reconcile this information if it is missing. --- .../templates/backrest-repo-config.yaml | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 examples/helm/create-cluster/templates/backrest-repo-config.yaml diff --git a/examples/helm/create-cluster/templates/backrest-repo-config.yaml b/examples/helm/create-cluster/templates/backrest-repo-config.yaml deleted file mode 100644 index 166d0b3dcd..0000000000 --- a/examples/helm/create-cluster/templates/backrest-repo-config.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: v1 -data: - authorized_keys: {{ .Files.Get "certs/hippo-key.pub" | b64enc }} - config: SG9zdCAqClN0cmljdEhvc3RLZXlDaGVja2luZyBubwpJZGVudGl0eUZpbGUgL3RtcC9pZF9lZDI1NTE5ClBvcnQgMjAyMgpVc2VyIHBnYmFja3Jlc3QK - id_ed25519: {{ .Files.Get "certs/hippo-key" | b64enc }} - ssh_host_ed25519_key: {{ .Files.Get "certs/hippo-key" | b64enc }} - sshd_config: IwkkT3BlbkJTRDogc3NoZF9jb25maWcsdiAxLjEwMCAyMDE2LzA4LzE1IDEyOjMyOjA0IG5hZGR5IEV4cCAkCgojIFRoaXMgaXMgdGhlIHNzaGQgc2VydmVyIHN5c3RlbS13aWRlIGNvbmZpZ3VyYXRpb24gZmlsZS4gIFNlZQojIHNzaGRfY29uZmlnKDUpIGZvciBtb3JlIGluZm9ybWF0aW9uLgoKIyBUaGlzIHNzaGQgd2FzIGNvbXBpbGVkIHdpdGggUEFUSD0vdXNyL2xvY2FsL2JpbjovdXNyL2JpbgoKIyBUaGUgc3RyYXRlZ3kgdXNlZCBmb3Igb3B0aW9ucyBpbiB0aGUgZGVmYXVsdCBzc2hkX2NvbmZpZyBzaGlwcGVkIHdpdGgKIyBPcGVuU1NIIGlzIHRvIHNwZWNpZnkgb3B0aW9ucyB3aXRoIHRoZWlyIGRlZmF1bHQgdmFsdWUgd2hlcmUKIyBwb3NzaWJsZSwgYnV0IGxlYXZlIHRoZW0gY29tbWVudGVkLiAgVW5jb21tZW50ZWQgb3B0aW9ucyBvdmVycmlkZSB0aGUKIyBkZWZhdWx0IHZhbHVlLgoKIyBJZiB5b3Ugd2FudCB0byBjaGFuZ2UgdGhlIHBvcnQgb24gYSBTRUxpbnV4IHN5c3RlbSwgeW91IGhhdmUgdG8gdGVsbAojIFNFTGludXggYWJvdXQgdGhpcyBjaGFuZ2UuCiMgc2VtYW5hZ2UgcG9ydCAtYSAtdCBzc2hfcG9ydF90IC1wIHRjcCAjUE9SVE5VTUJFUgojClBvcnQgMjAyMgojQWRkcmVzc0ZhbWlseSBhbnkKI0xpc3RlbkFkZHJlc3MgMC4wLjAuMAojTGlzdGVuQWRkcmVzcyA6OgoKSG9zdEtleSAvc3NoZC9zc2hfaG9zdF9lZDI1NTE5X2tleQoKIyBDaXBoZXJzIGFuZCBrZXlpbmcKI1Jla2V5TGltaXQgZGVmYXVsdCBub25lCgojIExvZ2dpbmcKI1N5c2xvZ0ZhY2lsaXR5IEFVVEgKU3lzbG9nRmFjaWxpdHkgQVVUSFBSSVYKI0xvZ0xldmVsIElORk8KCiMgQXV0aGVudGljYXRpb246CgojTG9naW5HcmFjZVRpbWUgMm0KUGVybWl0Um9vdExvZ2luIG5vClN0cmljdE1vZGVzIG5vCiNNYXhBdXRoVHJpZXMgNgojTWF4U2Vzc2lvbnMgMTAKClB1YmtleUF1dGhlbnRpY2F0aW9uIHllcwoKIyBUaGUgZGVmYXVsdCBpcyB0byBjaGVjayBib3RoIC5zc2gvYXV0aG9yaXplZF9rZXlzIGFuZCAuc3NoL2F1dGhvcml6ZWRfa2V5czIKIyBidXQgdGhpcyBpcyBvdmVycmlkZGVuIHNvIGluc3RhbGxhdGlvbnMgd2lsbCBvbmx5IGNoZWNrIC5zc2gvYXV0aG9yaXplZF9rZXlzCiNBdXRob3JpemVkS2V5c0ZpbGUJL3BnY29uZi9hdXRob3JpemVkX2tleXMKQXV0aG9yaXplZEtleXNGaWxlCS9zc2hkL2F1dGhvcml6ZWRfa2V5cwoKI0F1dGhvcml6ZWRQcmluY2lwYWxzRmlsZSBub25lCgojQXV0aG9yaXplZEtleXNDb21tYW5kIG5vbmUKI0F1dGhvcml6ZWRLZXlzQ29tbWFuZFVzZXIgbm9ib2R5CgojIEZvciB0aGlzIHRvIHdvcmsgeW91IHdpbGwgYWxzbyBuZWVkIGhvc3Qga2V5cyBpbiAvZXRjL3NzaC9zc2hfa25vd25faG9zdHMKI0hvc3RiYXNlZEF1dGhlbnRpY2F0aW9uIG5vCiMgQ2hhbmdlIHRvIHllcyBpZiB5b3UgZG9uJ3QgdHJ1c3Qgfi8uc3NoL2tub3duX2hvc3RzIGZvcgojIEhvc3RiYXNlZEF1dGhlbnRpY2F0aW9uCiNJZ25vcmVVc2VyS25vd25Ib3N0cyBubwojIERvbid0IHJlYWQgdGhlIHVzZXIncyB+Ly5yaG9zdHMgYW5kIH4vLnNob3N0cyBmaWxlcwojSWdub3JlUmhvc3RzIHllcwoKIyBUbyBkaXNhYmxlIHR1bm5lbGVkIGNsZWFyIHRleHQgcGFzc3dvcmRzLCBjaGFuZ2UgdG8gbm8gaGVyZSEKI1Bhc3N3b3JkQXV0aGVudGljYXRpb24geWVzCiNQZXJtaXRFbXB0eVBhc3N3b3JkcyBubwpQYXNzd29yZEF1dGhlbnRpY2F0aW9uIG5vCgojIENoYW5nZSB0byBubyB0byBkaXNhYmxlIHMva2V5IHBhc3N3b3JkcwpDaGFsbGVuZ2VSZXNwb25zZUF1dGhlbnRpY2F0aW9uIHllcwojQ2hhbGxlbmdlUmVzcG9uc2VBdXRoZW50aWNhdGlvbiBubwoKIyBLZXJiZXJvcyBvcHRpb25zCiNLZXJiZXJvc0F1dGhlbnRpY2F0aW9uIG5vCiNLZXJiZXJvc09yTG9jYWxQYXNzd2QgeWVzCiNLZXJiZXJvc1RpY2tldENsZWFudXAgeWVzCiNLZXJiZXJvc0dldEFGU1Rva2VuIG5vCiNLZXJiZXJvc1VzZUt1c2Vyb2sgeWVzCgojIEdTU0FQSSBvcHRpb25zCiNHU1NBUElBdXRoZW50aWNhdGlvbiB5ZXMKI0dTU0FQSUNsZWFudXBDcmVkZW50aWFscyBubwojR1NTQVBJU3RyaWN0QWNjZXB0b3JDaGVjayB5ZXMKI0dTU0FQSUtleUV4Y2hhbmdlIG5vCiNHU1NBUElFbmFibGVrNXVzZXJzIG5vCgojIFNldCB0aGlzIHRvICd5ZXMnIHRvIGVuYWJsZSBQQU0gYXV0aGVudGljYXRpb24sIGFjY291bnQgcHJvY2Vzc2luZywKIyBhbmQgc2Vzc2lvbiBwcm9jZXNzaW5nLiBJZiB0aGlzIGlzIGVuYWJsZWQsIFBBTSBhdXRoZW50aWNhdGlvbiB3aWxsCiMgYmUgYWxsb3dlZCB0aHJvdWdoIHRoZSBDaGFsbGVuZ2VSZXNwb25zZUF1dGhlbnRpY2F0aW9uIGFuZAojIFBhc3N3b3JkQXV0aGVudGljYXRpb24uICBEZXBlbmRpbmcgb24geW91ciBQQU0gY29uZmlndXJhdGlvbiwKIyBQQU0gYXV0aGVudGljYXRpb24gdmlhIENoYWxsZW5nZVJlc3BvbnNlQXV0aGVudGljYXRpb24gbWF5IGJ5cGFzcwojIHRoZSBzZXR0aW5nIG9mICJQZXJtaXRSb290TG9naW4gd2l0aG91dC1wYXNzd29yZCIuCiMgSWYgeW91IGp1c3Qgd2FudCB0aGUgUEFNIGFjY291bnQgYW5kIHNlc3Npb24gY2hlY2tzIHRvIHJ1biB3aXRob3V0CiMgUEFNIGF1dGhlbnRpY2F0aW9uLCB0aGVuIGVuYWJsZSB0aGlzIGJ1dCBzZXQgUGFzc3dvcmRBdXRoZW50aWNhdGlvbgojIGFuZCBDaGFsbGVuZ2VSZXNwb25zZUF1dGhlbnRpY2F0aW9uIHRvICdubycuCiMgV0FSTklORzogJ1VzZVBBTSBubycgaXMgbm90IHN1cHBvcnRlZCBpbiBSZWQgSGF0IEVudGVycHJpc2UgTGludXggYW5kIG1heSBjYXVzZSBzZXZlcmFsCiMgcHJvYmxlbXMuClVzZVBBTSB5ZXMKCiNBbGxvd0FnZW50Rm9yd2FyZGluZyB5ZXMKI0FsbG93VGNwRm9yd2FyZGluZyB5ZXMKI0dhdGV3YXlQb3J0cyBubwpYMTFGb3J3YXJkaW5nIHllcwojWDExRGlzcGxheU9mZnNldCAxMAojWDExVXNlTG9jYWxob3N0IHllcwojUGVybWl0VFRZIHllcwojUHJpbnRNb3RkIHllcwojUHJpbnRMYXN0TG9nIHllcwojVENQS2VlcEFsaXZlIHllcwojVXNlTG9naW4gbm8KVXNlUHJpdmlsZWdlU2VwYXJhdGlvbiBubwojUGVybWl0VXNlckVudmlyb25tZW50IG5vCiNDb21wcmVzc2lvbiBkZWxheWVkCiNDbGllbnRBbGl2ZUludGVydmFsIDAKI0NsaWVudEFsaXZlQ291bnRNYXggMwojU2hvd1BhdGNoTGV2ZWwgbm8KI1VzZUROUyB5ZXMKI1BpZEZpbGUgL3Zhci9ydW4vc3NoZC5waWQKI01heFN0YXJ0dXBzIDEwOjMwOjEwMAojUGVybWl0VHVubmVsIG5vCiNDaHJvb3REaXJlY3Rvcnkgbm9uZQojVmVyc2lvbkFkZGVuZHVtIG5vbmUKCiMgbm8gZGVmYXVsdCBiYW5uZXIgcGF0aAojQmFubmVyIG5vbmUKCiMgQWNjZXB0IGxvY2FsZS1yZWxhdGVkIGVudmlyb25tZW50IHZhcmlhYmxlcwpBY2NlcHRFbnYgTEFORyBMQ19DVFlQRSBMQ19OVU1FUklDIExDX1RJTUUgTENfQ09MTEFURSBMQ19NT05FVEFSWSBMQ19NRVNTQUdFUwpBY2NlcHRFbnYgTENfUEFQRVIgTENfTkFNRSBMQ19BRERSRVNTIExDX1RFTEVQSE9ORSBMQ19NRUFTVVJFTUVOVApBY2NlcHRFbnYgTENfSURFTlRJRklDQVRJT04gTENfQUxMIExBTkdVQUdFCkFjY2VwdEVudiBYTU9ESUZJRVJTCgojIG92ZXJyaWRlIGRlZmF1bHQgb2Ygbm8gc3Vic3lzdGVtcwpTdWJzeXN0ZW0Jc2Z0cAkvdXNyL2xpYmV4ZWMvb3BlbnNzaC9zZnRwLXNlcnZlcgoKIyBFeGFtcGxlIG9mIG92ZXJyaWRpbmcgc2V0dGluZ3Mgb24gYSBwZXItdXNlciBiYXNpcwojTWF0Y2ggVXNlciBhbm9uY3ZzCiMJWDExRm9yd2FyZGluZyBubwojCUFsbG93VGNwRm9yd2FyZGluZyBubwojCVBlcm1pdFRUWSBubwojCUZvcmNlQ29tbWFuZCBjdnMgc2VydmVyCg== -kind: Secret -metadata: - labels: - pg-cluster: {{ .Values.pgclustername }} - pgo-backrest-repo: "true" - vendor: crunchydata - name: {{ .Values.pgclustername }}-backrest-repo-config - namespace: {{ .Values.namespace }} -type: Opaque From c0588be9d7df3ffc7c2c8ba9190a69db558c6cae Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 9 Nov 2020 18:11:57 -0500 Subject: [PATCH 006/276] Indicate support for PL/Perl PL/Perl support was added to the PostGIS-enabled containers, as such, the Operator can now support it. Issue: CrunchyData/crunchy-containers#1287 --- README.md | 1 + docs/content/_index.md | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index f94ec04a6c..a58837efa6 100644 --- a/README.md +++ b/README.md @@ -189,6 +189,7 @@ There is also a `pgo-client` container if you wish to deploy the client directly - [PostgreSQL](https://www.postgresql.org) - [PostgreSQL Contrib Modules](https://www.postgresql.org/docs/current/contrib.html) - [PL/Python + PL/Python 3](https://www.postgresql.org/docs/current/plpython.html) + - [PL/Perl](https://www.postgresql.org/docs/current/plperl.html) - [pgAudit](https://www.pgaudit.org/) - [pgAudit Analyze](https://github.com/pgaudit/pgaudit_analyze) - [pgnodemx](https://github.com/CrunchyData/pgnodemx) diff --git a/docs/content/_index.md b/docs/content/_index.md index f83a7c49e1..b879f3db2a 100644 --- a/docs/content/_index.md +++ b/docs/content/_index.md @@ -104,6 +104,7 @@ The Crunchy PostgreSQL Operator extends Kubernetes to provide a higher-level abs - [PostgreSQL](https://www.postgresql.org) - [PostgreSQL Contrib Modules](https://www.postgresql.org/docs/current/contrib.html) - [PL/Python + PL/Python 3](https://www.postgresql.org/docs/current/plpython.html) + - [PL/Perl](https://www.postgresql.org/docs/current/plperl.html) - [pgAudit](https://www.pgaudit.org/) - [pgAudit Analyze](https://github.com/pgaudit/pgaudit_analyze) - [pgnodemx](https://github.com/CrunchyData/pgnodemx) From 70d17aaff3cf1bad47a871ad0c492d6020877a25 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 10 Nov 2020 08:34:09 -0500 Subject: [PATCH 007/276] Add explanation for transient monitoring issue This issue only occurs during the initialization phase of a PostgreSQL cluster, but can be confusing as it indicates there is an error. Issue: #1962 --- docs/content/tutorial/create-cluster.md | 23 ++++++++++++++++++++++ docs/content/tutorial/customize-cluster.md | 23 ++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/docs/content/tutorial/create-cluster.md b/docs/content/tutorial/create-cluster.md index eeb798faf5..423f0a4dbb 100644 --- a/docs/content/tutorial/create-cluster.md +++ b/docs/content/tutorial/create-cluster.md @@ -120,6 +120,29 @@ Also ensure that you have enough persistent volumes available: your Kubernetes a The most common occurrence of this is due to the Kubernetes network blocking SSH connections between Pods. Ensure that your Kubernetes networking layer allows for SSH connections over port 2022 in the Namespace that you are deploying your PostgreSQL clusters into. +### PostgreSQL Pod reports "Authentication Failed for `ccp_monitoring`" + +This is a temporary error that occurs when a new PostgreSQL cluster is first +initialized with the `--metrics` flag. The `crunchy-postgres-exporter` container +within the PostgreSQL Pod may be ready before the container with PostgreSQL is +ready. If a message in your logs further down displays a timestamp, e.g.: + +``` + now +------------------------------- +2020-11-10 08:23:15.968196-05 +``` + +Then the `ccp_monitoring` user is properly reconciled with the PostgreSQL +cluster. + +If the error message does not go away, this could indicate a few things: + +- The PostgreSQL instance has not initialized. Check to ensure that PostgreSQL +has successfully started. +- The password for the `ccp_monitoring` user has changed. In this case you will +need to update the Secret with the monitoring credentials. + ## Next Steps Once your cluster is created, the next step is to [connect to your PostgreSQL cluster]({{< relref "tutorial/connect-cluster.md" >}}). You can also [learn how to customize your PostgreSQL cluster]({{< relref "tutorial/customize-cluster.md" >}})! diff --git a/docs/content/tutorial/customize-cluster.md b/docs/content/tutorial/customize-cluster.md index e9be31c268..2fee92bb0a 100644 --- a/docs/content/tutorial/customize-cluster.md +++ b/docs/content/tutorial/customize-cluster.md @@ -184,6 +184,29 @@ There are many reasons why a PostgreSQL Pod may not be scheduled: - **Node affinity rules cannot be satisfied**. If you assigned a node label, ensure that the Nodes with that label are available for scheduling. If they are, ensure that there are enough resources available. - **Pod anti-affinity rules cannot be satisfied**. This most likely happens when [pod anti-affinity]({{< relref "architecture/high-availability/_index.md" >}}#how-the-crunchy-postgresql-operator-uses-pod-anti-affinity) is set to `required` and there are not enough Nodes available for scheduling. Consider adding more Nodes or relaxing your anti-affinity rules. +### PostgreSQL Pod reports "Authentication Failed for `ccp_monitoring`" + +This is a temporary error that occurs when a new PostgreSQL cluster is first +initialized with the `--metrics` flag. The `crunchy-postgres-exporter` container +within the PostgreSQL Pod may be ready before the container with PostgreSQL is +ready. If a message in your logs further down displays a timestamp, e.g.: + +``` + now +------------------------------- +2020-11-10 08:23:15.968196-05 +``` + +Then the `ccp_monitoring` user is properly reconciled with the PostgreSQL +cluster. + +If the error message does not go away, this could indicate a few things: + +- The PostgreSQL instance has not initialized. Check to ensure that PostgreSQL +has successfully started. +- The password for the `ccp_monitoring` user has changed. In this case you will +need to update the Secret with the monitoring credentials. + ## Next Steps As mentioned at the beginning, there are a lot more customizations that you can make to your PostgreSQL cluster, and we will cover those as the tutorial progresses! This section was to get you familiar with some of the most common customizations, and to explore how many options `pgo create cluster` has! From b25c6b1a6e646380f4847de3c26f87cabb503188 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 10 Nov 2020 09:12:40 -0500 Subject: [PATCH 008/276] Bump to v4.5.1 However, the README is staying on v4.5.0 to avoid confusion for people trying to install the Operator before the v4.5.1 containers are made available. --- Makefile | 4 +- bin/push-ccp-to-gcr.sh | 2 +- conf/postgres-operator/pgo.yaml | 4 +- docs/config.toml | 16 ++++---- docs/content/Configuration/compatibility.md | 10 ++++- docs/content/releases/4.5.1.md | 38 +++++++++++++++++++ docs/content/tutorial/pgbouncer.md | 2 +- examples/create-by-resource/fromcrd.json | 6 +-- examples/envs.sh | 2 +- .../create-cluster/templates/pgcluster.yaml | 2 +- examples/helm/create-cluster/values.yaml | 4 +- installers/ansible/README.md | 2 +- installers/ansible/values.yaml | 6 +-- installers/gcp-marketplace/Makefile | 2 +- installers/gcp-marketplace/README.md | 2 +- installers/gcp-marketplace/values.yaml | 6 +-- installers/helm/Chart.yaml | 2 +- installers/helm/values.yaml | 6 +-- installers/kubectl/client-setup.sh | 2 +- .../kubectl/postgres-operator-ocp311.yml | 8 ++-- installers/kubectl/postgres-operator.yml | 8 ++-- installers/metrics/ansible/README.md | 2 +- installers/metrics/helm/Chart.yaml | 2 +- installers/metrics/helm/helm_template.yaml | 2 +- installers/metrics/helm/values.yaml | 2 +- .../postgres-operator-metrics-ocp311.yml | 2 +- .../kubectl/postgres-operator-metrics.yml | 2 +- installers/olm/Makefile | 4 +- pkg/apis/crunchydata.com/v1/doc.go | 8 ++-- pkg/apiservermsgs/common.go | 2 +- redhat/atomic/help.1 | 2 +- redhat/atomic/help.md | 2 +- 32 files changed, 105 insertions(+), 59 deletions(-) create mode 100644 docs/content/releases/4.5.1.md diff --git a/Makefile b/Makefile index df717f4a70..19e150820a 100644 --- a/Makefile +++ b/Makefile @@ -5,9 +5,9 @@ PGOROOT ?= $(CURDIR) PGO_BASEOS ?= centos7 PGO_IMAGE_PREFIX ?= crunchydata PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) -PGO_VERSION ?= 4.5.0 +PGO_VERSION ?= 4.5.1 PGO_PG_VERSION ?= 12 -PGO_PG_FULLVERSION ?= 12.4 +PGO_PG_FULLVERSION ?= 12.5 PGO_BACKREST_VERSION ?= 2.29 PACKAGER ?= yum diff --git a/bin/push-ccp-to-gcr.sh b/bin/push-ccp-to-gcr.sh index 3b9de84ed0..59e2e329e8 100755 --- a/bin/push-ccp-to-gcr.sh +++ b/bin/push-ccp-to-gcr.sh @@ -16,7 +16,7 @@ GCR_IMAGE_PREFIX=gcr.io/crunchy-dev-test CCP_IMAGE_PREFIX=crunchydata -CCP_IMAGE_TAG=centos7-12.4-4.5.0 +CCP_IMAGE_TAG=centos7-12.5-4.5.1 IMAGES=( crunchy-prometheus diff --git a/conf/postgres-operator/pgo.yaml b/conf/postgres-operator/pgo.yaml index ff5c97ec7f..622fc268f8 100644 --- a/conf/postgres-operator/pgo.yaml +++ b/conf/postgres-operator/pgo.yaml @@ -2,7 +2,7 @@ Cluster: CCPImagePrefix: registry.developers.crunchydata.com/crunchydata Metrics: false Badger: false - CCPImageTag: centos7-12.4-4.5.0 + CCPImageTag: centos7-12.5-4.5.1 Port: 5432 PGBadgerPort: 10000 ExporterPort: 9187 @@ -82,4 +82,4 @@ Storage: Pgo: Audit: false PGOImagePrefix: registry.developers.crunchydata.com/crunchydata - PGOImageTag: centos7-4.5.0 + PGOImageTag: centos7-4.5.1 diff --git a/docs/config.toml b/docs/config.toml index 48ef2d760d..dc39d49daa 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -25,14 +25,14 @@ disableNavChevron = false # set true to hide next/prev chevron, default is false highlightClientSide = false # set true to use highlight.pack.js instead of the default hugo chroma highlighter menushortcutsnewtab = true # set true to open shortcuts links to a new tab/window enableGitInfo = true -operatorVersion = "4.5.0" -postgresVersion = "12.4" -postgresVersion13 = "13.0" -postgresVersion12 = "12.4" -postgresVersion11 = "11.9" -postgresVersion10 = "10.14" -postgresVersion96 = "9.6.19" -postgresVersion95 = "9.5.23" +operatorVersion = "4.5.1" +postgresVersion = "12.5" +postgresVersion13 = "13.1" +postgresVersion12 = "12.5" +postgresVersion11 = "11.10" +postgresVersion10 = "10.15" +postgresVersion96 = "9.6.20" +postgresVersion95 = "9.5.24" postgisVersion = "3.0" centosBase = "centos7" diff --git a/docs/content/Configuration/compatibility.md b/docs/content/Configuration/compatibility.md index b805ef9c08..43af7021db 100644 --- a/docs/content/Configuration/compatibility.md +++ b/docs/content/Configuration/compatibility.md @@ -12,7 +12,15 @@ version dependencies between the two projects. Below are the operator releases a | Operator Release | Container Release | Postgres | PgBackrest Version |:----------|:-------------|:------------|:-------------- -| 4.5.0 | 4.5.0 | 12.4 | 2.29 | +| 4.5.1 | 4.5.1 | 13.1 | 2.29 | +|||12.5|2.29| +|||11.10|2.29| +|||10.15|2.29| +|||9.6.20|2.29| +|||9.5.24|2.29| +|||| +| 4.5.0 | 4.5.0 | 13.0 | 2.29 | +|||12.4|2.29| |||11.9|2.29| |||10.14|2.29| |||9.6.19|2.29| diff --git a/docs/content/releases/4.5.1.md b/docs/content/releases/4.5.1.md new file mode 100644 index 0000000000..eeed22c013 --- /dev/null +++ b/docs/content/releases/4.5.1.md @@ -0,0 +1,38 @@ +--- +title: "4.5.1" +date: +draft: false +weight: 69 +--- + +Crunchy Data announces the release of the PostgreSQL Operator 4.5.1 on November 13, 2020. + +The PostgreSQL Operator is released in conjunction with the [Crunchy Container Suite](https://github.com/CrunchyData/crunchy-containers/). + +PostgreSQL Operator 4.5.1 release includes the following software versions upgrades: + +- [PostgreSQL](https://www.postgresql.org) is now at versions 13.1, 12.5, 11.10, 10.15, 9.6.20, and 9.5.24. +- [Patroni](https://patroni.readthedocs.io/) is now at version 2.0.1. +- PL/Perl can now be used in the PostGIS-enabled containers. + +## Changes + +- Simplified creation of a PostgreSQL cluster from a `pgcluster` resource. A user no longer has to provide a pgBackRest repository Secret: the Postgres Operator will now automatically generate this. +- The exposed ports for Services associated with a cluster is now available from the `pgo show cluster` command. +- If the `pgo-config` ConfigMap is not created during the installation of the Postgres Operator, the Postgres Operator will generate one when it initializes. +- Providing a value for `pgo_admin_password` in the installer is now optional. If no value is provided, the password for the initial administrative user is randomly generated. +- Added an example for how to create a PostgreSQL cluster that uses S3 for pgBackRest backups via a custom resource. + +## Fixes + +- Fix readiness check for a standby leader. Previously, the standby leader would not report as ready, even though it was. Reported by Alec Rooney (@alrooney). +- Proper determination if a `pgcluster` custom resource creation has been processed by its corresponding Postgres Operator controller. This prevents the custom resource from being run by the creation logic multiple times. +- Prevent `initdb` (cluster reinitialization) from occurring if the PostgreSQL container cannot initialize while bootstrapping from an existing PGDATA directory. +- Fix issue with UBI 8 / CentOS 8 when running a pgBackRest bootstrap or restore job, where duplicate "repo types" could be set. Specifically, the ensures the name of the repo type is set via the `PGBACKREST_REPO1_TYPE` environmental variable. Reported by Alec Rooney (@alrooney). +- Ensure external WAL and Tablespace PVCs are fully recreated during a restore. Reported by (@aurelien43). +- Ensure `pgo show backup` will work regardless of state of any of the PostgreSQL clusters. This pulls the information directly from the pgBackRest Pod itself. Reported by (@saltenhub). +- Ensure that sidecars (e.g. metrics collection, pgAdmin 4, pgBouncer) are deployable when using the PostGIS-enabled PostgreSQL image. Reported by Jean-Denis Giguère (@jdenisgiguere). +- Allow for special characters in pgBackRest environmental variables. Reported by (@SockenSalat). +- Ensure password for the `pgbouncer` administrative user stays synchronized between an existing Kubernetes Secret and PostgreSQL should the pgBouncer be recreated. +- When uninstalling an instance of the Postgres Operator in a Kubernetes cluster that has multiple instances of the Postgres Operator, ensure that only the requested instance to be uninstalled is the one that's uninstalled. +- The logger no longer defaults to using a log level of `DEBUG`. diff --git a/docs/content/tutorial/pgbouncer.md b/docs/content/tutorial/pgbouncer.md index 89ba8ce993..0349ff1eaf 100644 --- a/docs/content/tutorial/pgbouncer.md +++ b/docs/content/tutorial/pgbouncer.md @@ -116,7 +116,7 @@ PGPASSWORD=randompassword psql -h localhost -p 5432 -U pgbouncer pgbouncer You should see something similar to this: ``` -psql (12.4, server 1.14.0/bouncer) +psql (12.5, server 1.14.0/bouncer) Type "help" for help. pgbouncer=# diff --git a/examples/create-by-resource/fromcrd.json b/examples/create-by-resource/fromcrd.json index 987ec53d55..ea603808ac 100644 --- a/examples/create-by-resource/fromcrd.json +++ b/examples/create-by-resource/fromcrd.json @@ -16,7 +16,7 @@ "pg-cluster": "fromcrd", "pg-pod-anti-affinity": "", "pgo-backrest": "true", - "pgo-version": "4.5.0", + "pgo-version": "4.5.1", "pgouser": "pgoadmin", "primary": "true" }, @@ -62,7 +62,7 @@ }, "backrestResources": {}, "ccpimage": "crunchy-postgres-ha", - "ccpimagetag": "centos7-12.4-4.5.0", + "ccpimagetag": "centos7-12.5-4.5.1", "clustername": "fromcrd", "customconfig": "", "database": "userdb", @@ -95,7 +95,7 @@ "userlabels": { "crunchy-postgres-exporter": "false", "pg-pod-anti-affinity": "", - "pgo-version": "4.5.0", + "pgo-version": "4.5.1", "pgouser": "pgoadmin", "pgo-backrest": "true" }, diff --git a/examples/envs.sh b/examples/envs.sh index 10758085bd..848a7252b5 100644 --- a/examples/envs.sh +++ b/examples/envs.sh @@ -20,7 +20,7 @@ export PGO_CONF_DIR=$PGOROOT/installers/ansible/roles/pgo-operator/files # the version of the Operator you run is set by these vars export PGO_IMAGE_PREFIX=registry.developers.crunchydata.com/crunchydata export PGO_BASEOS=centos7 -export PGO_VERSION=4.5.0 +export PGO_VERSION=4.5.1 export PGO_IMAGE_TAG=$PGO_BASEOS-$PGO_VERSION # for setting the pgo apiserver port, disabling TLS or not verifying TLS diff --git a/examples/helm/create-cluster/templates/pgcluster.yaml b/examples/helm/create-cluster/templates/pgcluster.yaml index 9dc5a4655d..0852b1f447 100644 --- a/examples/helm/create-cluster/templates/pgcluster.yaml +++ b/examples/helm/create-cluster/templates/pgcluster.yaml @@ -13,7 +13,7 @@ metadata: pg-cluster: {{ .Values.pgclustername }} pg-pod-anti-affinity: "" pgo-backrest: "true" - pgo-version: 4.5.0 + pgo-version: 4.5.1 pgouser: admin name: {{ .Values.pgclustername }} namespace: {{ .Values.namespace }} diff --git a/examples/helm/create-cluster/values.yaml b/examples/helm/create-cluster/values.yaml index 09438cb745..4add0e560f 100644 --- a/examples/helm/create-cluster/values.yaml +++ b/examples/helm/create-cluster/values.yaml @@ -4,11 +4,11 @@ # The values is for the namespace and the postgresql cluster name ccpimage: crunchy-postgres-ha ccpimageprefix: registry.developers.crunchydata.com/crunchydata -ccpimagetag: centos7-12.4-4.5.0 +ccpimagetag: centos7-12.5-4.5.1 namespace: pgo pgclustername: hippo pgoimageprefix: registry.developers.crunchydata.com/crunchydata -pgoversion: 4.5.0 +pgoversion: 4.5.1 hipposecretuser: "hippo" hipposecretpassword: "Supersecurepassword*" postgressecretuser: "postgres" diff --git a/installers/ansible/README.md b/installers/ansible/README.md index a9f0babd16..345d035037 100644 --- a/installers/ansible/README.md +++ b/installers/ansible/README.md @@ -4,7 +4,7 @@ Crunchy Data

-Latest Release: 4.5.0 +Latest Release: 4.5.1 ## General diff --git a/installers/ansible/values.yaml b/installers/ansible/values.yaml index 4eb672bcec..ebce0ed751 100644 --- a/installers/ansible/values.yaml +++ b/installers/ansible/values.yaml @@ -17,7 +17,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos7-12.4-4.5.0" +ccp_image_tag: "centos7-12.5-4.5.1" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -50,14 +50,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.5.0" +pgo_client_version: "4.5.1" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos7-4.5.0" +pgo_image_tag: "centos7-4.5.1" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/gcp-marketplace/Makefile b/installers/gcp-marketplace/Makefile index 5f4f0c6eb1..f10f5b7c27 100644 --- a/installers/gcp-marketplace/Makefile +++ b/installers/gcp-marketplace/Makefile @@ -6,7 +6,7 @@ MARKETPLACE_TOOLS ?= gcr.io/cloud-marketplace-tools/k8s/dev:$(MARKETPLACE_VERSIO MARKETPLACE_VERSION ?= 0.9.4 KUBECONFIG ?= $(HOME)/.kube/config PARAMETERS ?= {} -PGO_VERSION ?= 4.5.0 +PGO_VERSION ?= 4.5.1 IMAGE_BUILD_ARGS = --build-arg MARKETPLACE_VERSION='$(MARKETPLACE_VERSION)' \ --build-arg PGO_VERSION='$(PGO_VERSION)' diff --git a/installers/gcp-marketplace/README.md b/installers/gcp-marketplace/README.md index fd686764ad..af2e60f80c 100644 --- a/installers/gcp-marketplace/README.md +++ b/installers/gcp-marketplace/README.md @@ -59,7 +59,7 @@ Google Cloud Marketplace. ```shell IMAGE_REPOSITORY=gcr.io/crunchydata-public/postgres-operator - export PGO_VERSION=4.5.0 + export PGO_VERSION=4.5.1 export INSTALLER_IMAGE=${IMAGE_REPOSITORY}/deployer:${PGO_VERSION} export OPERATOR_IMAGE=${IMAGE_REPOSITORY}:${PGO_VERSION} export OPERATOR_IMAGE_API=${IMAGE_REPOSITORY}/pgo-apiserver:${PGO_VERSION} diff --git a/installers/gcp-marketplace/values.yaml b/installers/gcp-marketplace/values.yaml index cb0840b35b..e2ed852df1 100644 --- a/installers/gcp-marketplace/values.yaml +++ b/installers/gcp-marketplace/values.yaml @@ -10,7 +10,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos7-12.4-4.5.0" +ccp_image_tag: "centos7-12.5-4.5.1" create_rbac: "true" db_name: "" db_password_age_days: "0" @@ -32,9 +32,9 @@ pgo_admin_role_name: "pgoadmin" pgo_admin_username: "admin" pgo_client_container_install: "false" pgo_client_install: 'false' -pgo_client_version: "4.5.0" +pgo_client_version: "4.5.1" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos7-4.5.0" +pgo_image_tag: "centos7-4.5.1" pgo_installation_name: '${OPERATOR_NAME}' pgo_operator_namespace: '${OPERATOR_NAMESPACE}' scheduler_timeout: "3600" diff --git a/installers/helm/Chart.yaml b/installers/helm/Chart.yaml index 6d7ffeaa30..e7a55444cb 100644 --- a/installers/helm/Chart.yaml +++ b/installers/helm/Chart.yaml @@ -3,7 +3,7 @@ name: postgres-operator description: Crunchy PostgreSQL Operator Helm chart for Kubernetes type: application version: 0.1.0 -appVersion: 4.5.0 +appVersion: 4.5.1 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/crunchy_logo.png keywords: diff --git a/installers/helm/values.yaml b/installers/helm/values.yaml index 649436e0af..b2c5d441b2 100644 --- a/installers/helm/values.yaml +++ b/installers/helm/values.yaml @@ -37,7 +37,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos7-12.4-4.5.0" +ccp_image_tag: "centos7-12.5-4.5.1" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -70,14 +70,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.5.0" +pgo_client_version: "4.5.1" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos7-4.5.0" +pgo_image_tag: "centos7-4.5.1" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/kubectl/client-setup.sh b/installers/kubectl/client-setup.sh index 6956d63f6b..496f25abd4 100755 --- a/installers/kubectl/client-setup.sh +++ b/installers/kubectl/client-setup.sh @@ -14,7 +14,7 @@ # This script should be run after the operator has been deployed PGO_OPERATOR_NAMESPACE="${PGO_OPERATOR_NAMESPACE:-pgo}" PGO_USER_ADMIN="${PGO_USER_ADMIN:-pgouser-admin}" -PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.5.0}" +PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.5.1}" PGO_CLIENT_URL="https://github.com/CrunchyData/postgres-operator/releases/download/${PGO_CLIENT_VERSION}" PGO_CMD="${PGO_CMD-kubectl}" diff --git a/installers/kubectl/postgres-operator-ocp311.yml b/installers/kubectl/postgres-operator-ocp311.yml index 9978d052d3..977c9ea790 100644 --- a/installers/kubectl/postgres-operator-ocp311.yml +++ b/installers/kubectl/postgres-operator-ocp311.yml @@ -44,7 +44,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos7-12.4-4.5.0" + ccp_image_tag: "centos7-12.5-4.5.1" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -77,14 +77,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.5.0" + pgo_client_version: "4.5.1" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos7-4.5.0" + pgo_image_tag: "centos7-4.5.1" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -161,7 +161,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos7-4.5.0 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos7-4.5.1 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/kubectl/postgres-operator.yml b/installers/kubectl/postgres-operator.yml index 2b516ef2ca..971e436d20 100644 --- a/installers/kubectl/postgres-operator.yml +++ b/installers/kubectl/postgres-operator.yml @@ -138,7 +138,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos7-12.4-4.5.0" + ccp_image_tag: "centos7-12.5-4.5.1" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -171,14 +171,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.5.0" + pgo_client_version: "4.5.1" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos7-4.5.0" + pgo_image_tag: "centos7-4.5.1" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -268,7 +268,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos7-4.5.0 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos7-4.5.1 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/ansible/README.md b/installers/metrics/ansible/README.md index 1c047d1a85..57f68cd878 100644 --- a/installers/metrics/ansible/README.md +++ b/installers/metrics/ansible/README.md @@ -4,7 +4,7 @@ Crunchy Data

-Latest Release: 4.5.0 +Latest Release: 4.5.1 ## General diff --git a/installers/metrics/helm/Chart.yaml b/installers/metrics/helm/Chart.yaml index 603cab3982..520204c2d1 100644 --- a/installers/metrics/helm/Chart.yaml +++ b/installers/metrics/helm/Chart.yaml @@ -3,6 +3,6 @@ name: postgres-operator-monitoring description: Install for Crunchy PostgreSQL Operator Monitoring type: application version: 0.1.0 -appVersion: 4.5.0 +appVersion: 4.5.1 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/crunchy_logo.png \ No newline at end of file diff --git a/installers/metrics/helm/helm_template.yaml b/installers/metrics/helm/helm_template.yaml index d5e346dbc7..b328adba55 100644 --- a/installers/metrics/helm/helm_template.yaml +++ b/installers/metrics/helm/helm_template.yaml @@ -20,5 +20,5 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos7-4.5.0" +pgo_image_tag: "centos7-4.5.1" diff --git a/installers/metrics/helm/values.yaml b/installers/metrics/helm/values.yaml index 9f2ecefb63..616001b5ec 100644 --- a/installers/metrics/helm/values.yaml +++ b/installers/metrics/helm/values.yaml @@ -20,7 +20,7 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos7-4.5.0" +pgo_image_tag: "centos7-4.5.1" # ===================== # Configuration Options diff --git a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml index ca4daafd16..f4643fc126 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml @@ -96,7 +96,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos7-4.5.0 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos7-4.5.1 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/kubectl/postgres-operator-metrics.yml b/installers/metrics/kubectl/postgres-operator-metrics.yml index e1cc94fd5a..313698aaeb 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics.yml @@ -165,7 +165,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos7-4.5.0 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos7-4.5.1 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/olm/Makefile b/installers/olm/Makefile index 9fefafd441..ebc81698fa 100644 --- a/installers/olm/Makefile +++ b/installers/olm/Makefile @@ -2,7 +2,7 @@ .SUFFIXES: CCP_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -CCP_PG_FULLVERSION ?= 12.4 +CCP_PG_FULLVERSION ?= 12.5 CCP_POSTGIS_VERSION ?= 3.0 CONTAINER ?= docker KUBECONFIG ?= $(HOME)/.kube/config @@ -11,7 +11,7 @@ OLM_TOOLS ?= registry.localhost:5000/postgres-operator-olm-tools:$(OLM_SDK_VERSI OLM_VERSION ?= 0.15.1 PGO_BASEOS ?= centos7 PGO_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -PGO_VERSION ?= 4.5.0 +PGO_VERSION ?= 4.5.1 PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) CCP_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(PGO_VERSION) CCP_POSTGIS_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(CCP_POSTGIS_VERSION)-$(PGO_VERSION) diff --git a/pkg/apis/crunchydata.com/v1/doc.go b/pkg/apis/crunchydata.com/v1/doc.go index 3d5c49cd25..4c793e782f 100644 --- a/pkg/apis/crunchydata.com/v1/doc.go +++ b/pkg/apis/crunchydata.com/v1/doc.go @@ -53,7 +53,7 @@ cluster. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.5.0", + '{"ClientVersion":"4.5.1", "Namespace":"pgouser1", "Name":"mycluster", $PGO_APISERVER_URL/clusters @@ -72,7 +72,7 @@ show all of the clusters that are in the given namespace. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.5.0", + '{"ClientVersion":"4.5.1", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/showclusters @@ -82,7 +82,7 @@ $PGO_APISERVER_URL/showclusters curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.5.0", + '{"ClientVersion":"4.5.1", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/clustersdelete @@ -90,7 +90,7 @@ $PGO_APISERVER_URL/clustersdelete Schemes: http, https BasePath: / - Version: 4.5.0 + Version: 4.5.1 License: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0 Contact: Crunchy Data https://www.crunchydata.com/ diff --git a/pkg/apiservermsgs/common.go b/pkg/apiservermsgs/common.go index cce7f6be40..093405b4fd 100644 --- a/pkg/apiservermsgs/common.go +++ b/pkg/apiservermsgs/common.go @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -const PGO_VERSION = "4.5.0" +const PGO_VERSION = "4.5.1" // Ok status const Ok = "ok" diff --git a/redhat/atomic/help.1 b/redhat/atomic/help.1 index bc21518dd8..6f9bfad143 100644 --- a/redhat/atomic/help.1 +++ b/redhat/atomic/help.1 @@ -56,4 +56,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa \fB\fCRelease=\fR .PP -The specific release number of the container. For example, Release="4.5.0" +The specific release number of the container. For example, Release="4.5.1" diff --git a/redhat/atomic/help.md b/redhat/atomic/help.md index 8950e24d47..1a12dbc144 100644 --- a/redhat/atomic/help.md +++ b/redhat/atomic/help.md @@ -45,4 +45,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa `Release=` -The specific release number of the container. For example, Release="4.5.0" +The specific release number of the container. For example, Release="4.5.1" From a7c590fa7f776a784d43d71b853c2390610e8908 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Wed, 11 Nov 2020 19:58:45 -0500 Subject: [PATCH 009/276] Bump version value in README While the packages are not available, this is needed to finish the wrap. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a58837efa6..c0607435f1 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,7 @@ Based on your storage settings in your Kubernetes environment, you may be able t ```shell kubectl create namespace pgo -kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.5.0/installers/kubectl/postgres-operator.yml +kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.5.1/installers/kubectl/postgres-operator.yml ``` Otherwise, we highly recommend following the instructions from our [Quickstart](https://access.crunchydata.com/documentation/postgres-operator/latest/quickstart/). From b0a276ab1abf028ea806911ab5524915a68ea621 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 15 Nov 2020 14:14:49 -0500 Subject: [PATCH 010/276] Tighter check for replica service in `pgo test` The check looked to see if the word "replica" existed in one of the Service Labels, when really we shold be checking for a suffix of "replica". Issue: [ch9764] Issue: #2047 --- internal/apiserver/clusterservice/clusterimpl.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index 37ce1f0eab..8369d421ec 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -489,7 +489,7 @@ func TestCluster(name, selector, ns, pgouser string, allFlag bool) msgs.ClusterT switch { default: endpoint.InstanceType = msgs.ClusterTestInstanceTypePrimary - case strings.Contains(service.Name, msgs.PodTypeReplica): + case strings.HasSuffix(service.Name, msgs.PodTypeReplica): endpoint.InstanceType = msgs.ClusterTestInstanceTypeReplica case service.Pgbouncer: endpoint.InstanceType = msgs.ClusterTestInstanceTypePGBouncer From d359c355786bc83079cd5652d469cbc8db478fc1 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 16 Nov 2020 09:40:22 -0500 Subject: [PATCH 011/276] Update OLM installation instructions template This updates a few of the OLM installation instruction steps so that the dependency on `git` is removed, replaced by `curl` to get the exact files from the repository that are needed. --- installers/olm/description.openshift.md | 32 +++++++++++++------------ installers/olm/description.upstream.md | 32 +++++++++++++------------ 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/installers/olm/description.openshift.md b/installers/olm/description.openshift.md index e4eb3c0831..76be0028da 100644 --- a/installers/olm/description.openshift.md +++ b/installers/olm/description.openshift.md @@ -49,13 +49,6 @@ export PGO_OPERATOR_NAMESPACE=pgo oc create namespace "$PGO_OPERATOR_NAMESPACE" ``` -Next, clone the PostgreSQL Operator repository locally. - -``` -git clone -b v${PGO_VERSION} https://github.com/CrunchyData/postgres-operator.git -cd postgres-operator -``` - ### Security For the PostgreSQL Operator and PostgreSQL clusters to run in the recommended `restricted` [Security Context Constraint][], @@ -69,19 +62,23 @@ Configure pgBackRest for your environment. If you do not plan to use AWS S3 to s the `aws-s3` keys below. ``` +curl https://raw.githubusercontent.com/CrunchyData/postgres-operator/v${PGO_VERSION}/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/config > config +curl https://raw.githubusercontent.com/CrunchyData/postgres-operator/v${PGO_VERSION}/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/sshd_config > sshd_config +curl https://raw.githubusercontent.com/CrunchyData/postgres-operator/v${PGO_VERSION}/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/aws-s3-ca.crt > aws-s3-ca.crt + oc -n "$PGO_OPERATOR_NAMESPACE" create secret generic pgo-backrest-repo-config \ - --from-file=./installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/config \ - --from-file=./installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/sshd_config \ - --from-file=./installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/aws-s3-ca.crt \ + --from-file=./config \ + --from-file=./sshd_config \ + --from-file=./aws-s3-ca.crt \ --from-literal=aws-s3-key="" \ --from-literal=aws-s3-key-secret="" ``` ### Certificates (optional) -The PostgreSQL Operator has an API that uses TLS to communicate securely with clients. If you have -a certificate bundle validated by your organization, you can install it now. If not, the API will -automatically generate and use a self-signed certificate. +The PostgreSQL Operator has an API that uses TLS to communicate securely with clients. If one is not provided, the API will automatically generated one for you. + +If you have a certificate bundle validated by your organization, you can install it now. ``` oc -n "$PGO_OPERATOR_NAMESPACE" create secret tls pgo.tls \ @@ -102,8 +99,13 @@ to use the [PostgreSQL Operator Client][pgo-client]. Install the first set of client credentials and download the `pgo` binary and client certificates. ``` -PGO_CMD=oc ./deploy/install-bootstrap-creds.sh -PGO_CMD=oc ./installers/kubectl/client-setup.sh +curl https://raw.githubusercontent.com/CrunchyData/postgres-operator/v${PGO_VERSION}/deploy/install-bootstrap-creds.sh > install-bootstrap-creds.sh +curl https://raw.githubusercontent.com/CrunchyData/postgres-operator/v${PGO_VERSION}/installers/kubectl/client-setup.sh > client-setup.sh + +chmod +x install-bootstrap-creds.sh client-setup.sh + +PGO_CMD=oc ./install-bootstrap-creds.sh +PGO_CMD=oc ./client-setup.sh ``` The client needs to be able to reach the PostgreSQL Operator API from outside the OpenShift cluster. diff --git a/installers/olm/description.upstream.md b/installers/olm/description.upstream.md index 1e192fa9c2..7d1dcce69d 100644 --- a/installers/olm/description.upstream.md +++ b/installers/olm/description.upstream.md @@ -49,32 +49,29 @@ export PGO_OPERATOR_NAMESPACE=pgo kubectl create namespace "$PGO_OPERATOR_NAMESPACE" ``` -Next, clone the PostgreSQL Operator repository locally. - -``` -git clone -b v${PGO_VERSION} https://github.com/CrunchyData/postgres-operator.git -cd postgres-operator -``` - ### Secrets Configure pgBackRest for your environment. If you do not plan to use AWS S3 to store backups, you can omit the `aws-s3` keys below. ``` +curl https://raw.githubusercontent.com/CrunchyData/postgres-operator/v${PGO_VERSION}/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/config > config +curl https://raw.githubusercontent.com/CrunchyData/postgres-operator/v${PGO_VERSION}/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/sshd_config > sshd_config +curl https://raw.githubusercontent.com/CrunchyData/postgres-operator/v${PGO_VERSION}/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/aws-s3-ca.crt > aws-s3-ca.crt + kubectl -n "$PGO_OPERATOR_NAMESPACE" create secret generic pgo-backrest-repo-config \ - --from-file=./installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/config \ - --from-file=./installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/sshd_config \ - --from-file=./installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/aws-s3-ca.crt \ + --from-file=./config \ + --from-file=./sshd_config \ + --from-file=./aws-s3-ca.crt \ --from-literal=aws-s3-key="" \ --from-literal=aws-s3-key-secret="" ``` ### Certificates (optional) -The PostgreSQL Operator has an API that uses TLS to communicate securely with clients. If you have -a certificate bundle validated by your organization, you can install it now. If not, the API will -automatically generate and use a self-signed certificate. +The PostgreSQL Operator has an API that uses TLS to communicate securely with clients. If one is not provided, the API will automatically generated one for you. + +If you have a certificate bundle validated by your organization, you can install it now. ``` kubectl -n "$PGO_OPERATOR_NAMESPACE" create secret tls pgo.tls \ @@ -95,8 +92,13 @@ to use the [PostgreSQL Operator Client][pgo-client]. Install the first set of client credentials and download the `pgo` binary and client certificates. ``` -PGO_CMD=kubectl ./deploy/install-bootstrap-creds.sh -PGO_CMD=kubectl ./installers/kubectl/client-setup.sh +curl https://raw.githubusercontent.com/CrunchyData/postgres-operator/v${PGO_VERSION}/deploy/install-bootstrap-creds.sh > install-bootstrap-creds.sh +curl https://raw.githubusercontent.com/CrunchyData/postgres-operator/v${PGO_VERSION}/installers/kubectl/client-setup.sh > client-setup.sh + +chmod +x install-bootstrap-creds.sh client-setup.sh + +PGO_CMD=kubectl ./install-bootstrap-creds.sh +PGO_CMD=kubectl ./client-setup.sh ``` The client needs to be able to reach the PostgreSQL Operator API from outside the Kubernetes cluster. From 9462bdb5746d7e057097c80ca4568388405bb55f Mon Sep 17 00:00:00 2001 From: Chris Bandy Date: Tue, 3 Nov 2020 17:32:35 -0600 Subject: [PATCH 012/276] Trace Kubernetes API calls using OpenTelemetry OpenTelemetry offers instrumentation packages that are agnostic to the tool(s) that finally store telemetry data. When the SDK for Go reaches GA, we can leverage the OTLP exporter to offload further configuration to an external OpenTelemetry Collector. For now, traces can be sent to one of a handful of destinations chosen by environment variable: Jaeger agent, Jaeger collector, stdout as JSON, or a file as JSON. Issue: [ch9735] --- cmd/postgres-operator/main.go | 21 +- cmd/postgres-operator/open_telemetry.go | 101 +++++++++ go.mod | 9 +- go.sum | 191 ++++++++++++++++++ .../controller/manager/controllermanager.go | 6 +- internal/kubeapi/client_config.go | 13 +- 6 files changed, 333 insertions(+), 8 deletions(-) create mode 100644 cmd/postgres-operator/open_telemetry.go diff --git a/cmd/postgres-operator/main.go b/cmd/postgres-operator/main.go index 325303c9a2..e1884991be 100644 --- a/cmd/postgres-operator/main.go +++ b/cmd/postgres-operator/main.go @@ -39,6 +39,12 @@ import ( ) func main() { + if flush, err := initOpenTelemetry(); err != nil { + log.Error(err) + os.Exit(2) + } else { + defer flush() + } debugFlag := os.Getenv("CRUNCHY_DEBUG") //add logging configuration @@ -53,7 +59,18 @@ func main() { //give time for pgo-event to start up time.Sleep(time.Duration(5) * time.Second) - client, err := kubeapi.NewClient() + newKubernetesClient := func() (*kubeapi.Client, error) { + config, err := kubeapi.LoadClientConfig() + if err != nil { + return nil, err + } + + config.Wrap(otelTransportWrapper()) + + return kubeapi.NewClientForConfig(config) + } + + client, err := newKubernetesClient() if err != nil { log.Error(err) os.Exit(2) @@ -83,6 +100,8 @@ func main() { } log.Debug("controller manager created") + controllerManager.NewKubernetesClient = newKubernetesClient + // If not using the "disabled" namespace operating mode, start a real namespace controller // that is able to resond to namespace events in the Kube cluster. If using the "disabled" // operating mode, then create a fake client containing all namespaces defined for the install diff --git a/cmd/postgres-operator/open_telemetry.go b/cmd/postgres-operator/open_telemetry.go new file mode 100644 index 0000000000..382079687d --- /dev/null +++ b/cmd/postgres-operator/open_telemetry.go @@ -0,0 +1,101 @@ +package main + +/* +Copyright 2020 Crunchy Data +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. +*/ + +import ( + "fmt" + "io" + "net/http" + "os" + + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/otel/api/global" + "go.opentelemetry.io/otel/exporters/stdout" + "go.opentelemetry.io/otel/exporters/trace/jaeger" +) + +func initOpenTelemetry() (func(), error) { + // At the time of this writing, the SDK (go.opentelemetry.io/otel@v0.13.0) + // does not automatically initialize any trace or metric exporter. An upcoming + // specification details environment variables that should facilitate this in + // the future. + // + // - https://github.com/open-telemetry/opentelemetry-specification/blob/f5519f2b/specification/sdk-environment-variables.md + + switch os.Getenv("OTEL_EXPORTER") { + case "jaeger": + var endpoint jaeger.EndpointOption + agent := os.Getenv("JAEGER_AGENT_ENDPOINT") + collector := jaeger.CollectorEndpointFromEnv() + + if agent != "" { + endpoint = jaeger.WithAgentEndpoint(agent) + } + if collector != "" { + endpoint = jaeger.WithCollectorEndpoint(collector) + } + + provider, flush, err := jaeger.NewExportPipeline(endpoint) + if err != nil { + return nil, fmt.Errorf("unable to initialize Jaeger exporter: %w", err) + } + + global.SetTracerProvider(provider) + return flush, nil + + case "json": + var closer io.Closer + filename := os.Getenv("OTEL_JSON_FILE") + options := []stdout.Option{stdout.WithoutMetricExport()} + + if filename != "" { + file, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return nil, fmt.Errorf("unable to open exporter file: %w", err) + } + closer = file + options = append(options, stdout.WithWriter(file)) + } + + provider, pusher, err := stdout.NewExportPipeline(options, nil) + if err != nil { + return nil, fmt.Errorf("unable to initialize stdout exporter: %w", err) + } + flush := func() { + pusher.Stop() + if closer != nil { + _ = closer.Close() + } + } + + global.SetTracerProvider(provider) + return flush, nil + } + + // $OTEL_EXPORTER is unset or unknown, so no TracerProvider has been assigned. + // The default at this time is a single "no-op" tracer. + + return func() {}, nil +} + +// otelTransportWrapper creates a function that wraps the provided net/http.RoundTripper +// with one that starts a span for each request, injects context into that request, +// and ends the span when that request's response body is closed. +func otelTransportWrapper(options ...otelhttp.Option) func(http.RoundTripper) http.RoundTripper { + return func(rt http.RoundTripper) http.RoundTripper { + return otelhttp.NewTransport(rt, options...) + } +} diff --git a/go.mod b/go.mod index 0147d04c01..bef11e91fb 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,6 @@ go 1.15 require ( github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect github.com/fatih/color v1.9.0 - github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect - github.com/google/go-cmp v0.4.1 // indirect github.com/gorilla/mux v1.7.4 github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/mattn/go-colorable v0.1.6 // indirect @@ -16,9 +14,12 @@ require ( github.com/spf13/cobra v0.0.5 github.com/spf13/pflag v1.0.5 github.com/xdg/stringprep v1.0.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.13.0 + go.opentelemetry.io/otel v0.13.0 + go.opentelemetry.io/otel/exporters/stdout v0.13.0 + go.opentelemetry.io/otel/exporters/trace/jaeger v0.13.0 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 - golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect - golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a + golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 k8s.io/api v0.19.2 k8s.io/apimachinery v0.19.2 k8s.io/client-go v0.19.2 diff --git a/go.sum b/go.sum index 9be2bc52bd..d3dad7eb97 100644 --- a/go.sum +++ b/go.sum @@ -5,11 +5,32 @@ cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6A cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= @@ -25,6 +46,8 @@ github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6L github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/sketches-go v0.0.1 h1:RtG+76WKgZuz6FIaGsjoPePmadDBkuD/KC6+ZWu78b8= +github.com/DataDog/sketches-go v0.0.1/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -35,9 +58,13 @@ github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4Rq github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/apache/thrift v0.13.0 h1:5hryIiq9gtn+MiLVn0wP37kb/uTeRZgN08WoCsAhIhI= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/benbjohnson/clock v1.0.3 h1:vkLuvpK4fmtSCuo60+yC63p7y0BmQ8gm5ZXGuBCJyXg= +github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= @@ -47,6 +74,7 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= @@ -77,7 +105,9 @@ github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7fo github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -86,6 +116,8 @@ github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= @@ -94,7 +126,9 @@ github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2H github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg= @@ -160,11 +194,17 @@ github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4er github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -183,13 +223,22 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= @@ -343,11 +392,14 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -357,6 +409,10 @@ github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0= github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= @@ -365,6 +421,20 @@ go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qL go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opentelemetry.io/contrib v0.13.0 h1:q34CFu5REx9Dt2ksESHC/doIjFJkEg1oV3aSwlL5JR0= +go.opentelemetry.io/contrib v0.13.0/go.mod h1:HzCu6ebm0ywgNxGaEfs3izyJOMP4rZnzxycyTgpI5Sg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.13.0 h1:dnZy1afzxEDrHybTYoJE1bQ3fphNwZF2ipSsynlITP4= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.13.0/go.mod h1:SeQm4RTCcZ2/hlMSTuHb7nwIROe5odBtgfKx+7MMqEs= +go.opentelemetry.io/otel v0.13.0 h1:2isEnyzjjJZq6r2EKMsFj4TxiQiexsM04AVhwbR/oBA= +go.opentelemetry.io/otel v0.13.0/go.mod h1:dlSNewoRYikTkotEnxdmuBHgzT+k/idJSfDv/FxEnOY= +go.opentelemetry.io/otel/exporters/stdout v0.13.0 h1:A+XiGIPQbGoJoBOJfKAKnZyiUSjSWvL3XWETUvtom5k= +go.opentelemetry.io/otel/exporters/stdout v0.13.0/go.mod h1:JJt8RpNY6K+ft9ir3iKpceCvT/rhzJXEExGrWFCbv1o= +go.opentelemetry.io/otel/exporters/trace/jaeger v0.13.0 h1:TjXcUVYbsjl3lYifrWptraZAL0OBmpMxRLm/eJ1GyZU= +go.opentelemetry.io/otel/exporters/trace/jaeger v0.13.0/go.mod h1:RSg6E40NYGqN/aCrStCUue2e+jABeFk2bKdNucw63ao= +go.opentelemetry.io/otel/sdk v0.13.0 h1:4VCfpKamZ8GtnepXxMRurSpHpMKkcxhtO33z1S4rGDQ= +go.opentelemetry.io/otel/sdk v0.13.0/go.mod h1:dKvLH8Uu8LcEPlSAUsfW7kMGaJBhk/1NYvpPZ6wIMbU= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= @@ -387,7 +457,12 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -396,12 +471,18 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -419,21 +500,37 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 h1:ld7aEMNHoBnnDAX15v1T6z31v8HwR2A9FYOuAhWqkwc= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -442,6 +539,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -461,8 +560,10 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -470,12 +571,26 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4 h1:5/PjkGUjvEU5Gl6BxmvKRPpqo2uNMv4rcHBMwzk/st8= golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f h1:Fqb3ao1hUmOR3GkUOg/Y+BadLwykBIzs5q8Ez2SbHyc= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -508,25 +623,66 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0= gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.32.0 h1:Le77IccnTqEa8ryp9wIpX5W3zYm7Gf9LhOp9PHcwFts= +google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -535,15 +691,42 @@ google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -555,6 +738,8 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -577,12 +762,16 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI= k8s.io/api v0.19.2 h1:q+/krnHWKsL7OBZg/rxnycsl9569Pud76UJ77MvKXms= k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI= @@ -616,6 +805,8 @@ k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/ k8s.io/utils v0.0.0-20200729134348-d5654de09c73 h1:uJmqzgNWG7XyClnU/mLPBWwfKKF1K8Hf8whTseBgJcg= k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= sigs.k8s.io/controller-runtime v0.6.3 h1:SBbr+inLPEKhvlJtrvDcwIpm+uhDvp63Bl72xYJtoOE= sigs.k8s.io/controller-runtime v0.6.3/go.mod h1:WlZNXcM0++oyaQt4B7C2lEE5JYRs8vJUzRP4N4JpdAY= diff --git a/internal/controller/manager/controllermanager.go b/internal/controller/manager/controllermanager.go index bb2c2e1039..236ed1bb74 100644 --- a/internal/controller/manager/controllermanager.go +++ b/internal/controller/manager/controllermanager.go @@ -61,6 +61,8 @@ type ControllerManager struct { pgoConfig config.PgoConfig pgoNamespace string sem *semaphore.Weighted + + NewKubernetesClient func() (*kubeapi.Client, error) } // controllerGroup is a struct for managing the various controllers created to handle events @@ -90,6 +92,8 @@ func NewControllerManager(namespaces []string, pgoConfig: pgoConfig, pgoNamespace: pgoNamespace, sem: semaphore.NewWeighted(1), + + NewKubernetesClient: kubeapi.NewClient, } // create controller groups for each namespace provided @@ -229,7 +233,7 @@ func (c *ControllerManager) addControllerGroup(namespace string) error { } // create a client for kube resources - client, err := kubeapi.NewClient() + client, err := c.NewKubernetesClient() if err != nil { log.Error(err) return err diff --git a/internal/kubeapi/client_config.go b/internal/kubeapi/client_config.go index 5d070fc25e..22fe39e9b2 100644 --- a/internal/kubeapi/client_config.go +++ b/internal/kubeapi/client_config.go @@ -55,7 +55,9 @@ var _ Interface = &Client{} // CrunchydataV1 retrieves the CrunchydataV1Client func (c *Client) CrunchydataV1() crunchydatav1.CrunchydataV1Interface { return c.crunchydataV1 } -func loadClientConfig() (*rest.Config, error) { +// LoadClientConfig prepares a configuration from the environment or home directory, +// falling back to in-cluster when applicable. +func LoadClientConfig() (*rest.Config, error) { // The default loading rules try to read from the files specified in the // environment or from the home directory. loader := clientcmd.NewDefaultClientConfigLoadingRules() @@ -69,11 +71,18 @@ func loadClientConfig() (*rest.Config, error) { // NewClient returns a kubernetes.Clientset and its underlying configuration. func NewClient() (*Client, error) { - config, err := loadClientConfig() + config, err := LoadClientConfig() if err != nil { return nil, err } + return NewClientForConfig(config) +} + +// NewClientForConfig returns a kubernetes.Clientset using config. +func NewClientForConfig(config *rest.Config) (*Client, error) { + var err error + // Match the settings applied by sigs.k8s.io/controller-runtime@v0.6.0; // see https://github.com/kubernetes-sigs/controller-runtime/issues/365. if config.QPS == 0.0 { From 20c67488f54062c93d4fd0fe5dfd9c51cc01d4bd Mon Sep 17 00:00:00 2001 From: Chris Bandy Date: Fri, 13 Nov 2020 10:22:56 -0600 Subject: [PATCH 013/276] Use log.Fatal rather than os.Exit in cmd/postgres-operator --- cmd/postgres-operator/main.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/cmd/postgres-operator/main.go b/cmd/postgres-operator/main.go index e1884991be..aa7e99ab27 100644 --- a/cmd/postgres-operator/main.go +++ b/cmd/postgres-operator/main.go @@ -40,8 +40,7 @@ import ( func main() { if flush, err := initOpenTelemetry(); err != nil { - log.Error(err) - os.Exit(2) + log.Fatal(err) } else { defer flush() } @@ -72,8 +71,7 @@ func main() { client, err := newKubernetesClient() if err != nil { - log.Error(err) - os.Exit(2) + log.Fatal(err) } operator.Initialize(client) @@ -83,8 +81,7 @@ func main() { // list of target namespaces for the operator install namespaceList, err := operator.SetupNamespaces(client) if err != nil { - log.Errorf("Error configuring operator namespaces: %v", err) - os.Exit(2) + log.Fatalf("Error configuring operator namespaces: %v", err) } // set up signals so we handle the first shutdown signal gracefully @@ -95,8 +92,7 @@ func main() { controllerManager, err := manager.NewControllerManager(namespaceList, operator.Pgo, operator.PgoNamespace, operator.InstallationName, operator.NamespaceOperatingMode()) if err != nil { - log.Error(err) - os.Exit(2) + log.Fatal(err) } log.Debug("controller manager created") From 41dd921b53d8fcd1507b25b42714d33cd40f6174 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 17 Nov 2020 13:57:16 -0500 Subject: [PATCH 014/276] Bump Kubernetes client libs to 0.19.4 --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index bef11e91fb..e85242cd76 100644 --- a/go.mod +++ b/go.mod @@ -20,9 +20,9 @@ require ( go.opentelemetry.io/otel/exporters/trace/jaeger v0.13.0 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 - k8s.io/api v0.19.2 - k8s.io/apimachinery v0.19.2 - k8s.io/client-go v0.19.2 + k8s.io/api v0.19.4 + k8s.io/apimachinery v0.19.4 + k8s.io/client-go v0.19.4 sigs.k8s.io/controller-runtime v0.6.3 sigs.k8s.io/yaml v1.2.0 ) diff --git a/go.sum b/go.sum index d3dad7eb97..36a21fea4b 100644 --- a/go.sum +++ b/go.sum @@ -773,17 +773,17 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI= -k8s.io/api v0.19.2 h1:q+/krnHWKsL7OBZg/rxnycsl9569Pud76UJ77MvKXms= -k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI= +k8s.io/api v0.19.4 h1:I+1I4cgJYuCDgiLNjKx7SLmIbwgj9w7N7Zr5vSIdwpo= +k8s.io/api v0.19.4/go.mod h1:SbtJ2aHCItirzdJ36YslycFNzWADYH3tgOhvBEFtZAk= k8s.io/apiextensions-apiserver v0.18.6 h1:vDlk7cyFsDyfwn2rNAO2DbmUbvXy5yT5GE3rrqOzaMo= k8s.io/apiextensions-apiserver v0.18.6/go.mod h1:lv89S7fUysXjLZO7ke783xOwVTm6lKizADfvUM/SS/M= k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= -k8s.io/apimachinery v0.19.2 h1:5Gy9vQpAGTKHPVOh5c4plE274X8D/6cuEiTO2zve7tc= -k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= +k8s.io/apimachinery v0.19.4 h1:+ZoddM7nbzrDCp0T3SWnyxqf8cbWPT2fkZImoyvHUG0= +k8s.io/apimachinery v0.19.4/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= k8s.io/apiserver v0.18.6/go.mod h1:Zt2XvTHuaZjBz6EFYzpp+X4hTmgWGy8AthNVnTdm3Wg= k8s.io/client-go v0.18.6/go.mod h1:/fwtGLjYMS1MaM5oi+eXhKwG+1UHidUEXRh6cNsdO0Q= -k8s.io/client-go v0.19.2 h1:gMJuU3xJZs86L1oQ99R4EViAADUPMHHtS9jFshasHSc= -k8s.io/client-go v0.19.2/go.mod h1:S5wPhCqyDNAlzM9CnEdgTGV4OqhsW3jGO1UM1epwfJA= +k8s.io/client-go v0.19.4 h1:85D3mDNoLF+xqpyE9Dh/OtrJDyJrSRKkHmDXIbEzer8= +k8s.io/client-go v0.19.4/go.mod h1:ZrEy7+wj9PjH5VMBCuu/BDlvtUAku0oVFk4MmnW9mWA= k8s.io/code-generator v0.18.6/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= k8s.io/component-base v0.18.6/go.mod h1:knSVsibPR5K6EW2XOjEHik6sdU5nCvKMrzMt2D4In14= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= From 29ef4855cba68ddcc4dee13a21b697315e5fc88e Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 17 Nov 2020 13:49:57 -0500 Subject: [PATCH 015/276] Allow for Operator generation of deafult pgBackRest configuration The "pgo-backrest-repo-config" consists of several default files that can be generated when the Operator is initially loaded (and reconciled on initialization as well). This removes additional manual steps required when deploying the Operator through methods such as OLM, making it easier to get started. --- build/postgres-operator/Dockerfile | 1 + deploy/deploy.sh | 12 +-- .../installation/other/operator-hub.md | 39 +------- .../ansible/roles/pgo-operator/tasks/main.yml | 22 ++--- installers/olm/description.openshift.md | 12 +-- installers/olm/description.upstream.md | 12 +-- internal/config/secrets.go | 18 ++++ internal/operator/common.go | 90 +++++++++++++++++-- internal/util/cluster.go | 2 +- 9 files changed, 127 insertions(+), 81 deletions(-) create mode 100644 internal/config/secrets.go diff --git a/build/postgres-operator/Dockerfile b/build/postgres-operator/Dockerfile index d88621d73f..dd9895987e 100644 --- a/build/postgres-operator/Dockerfile +++ b/build/postgres-operator/Dockerfile @@ -28,6 +28,7 @@ RUN if [ "$DFSET" = "rhel" ] ; then \ fi ADD bin/postgres-operator /usr/local/bin +ADD installers/ansible/roles/pgo-operator/files/pgo-backrest-repo /default-pgo-backrest-repo ADD installers/ansible/roles/pgo-operator/files/pgo-configs /default-pgo-config ADD conf/postgres-operator/pgo.yaml /default-pgo-config/pgo.yaml diff --git a/deploy/deploy.sh b/deploy/deploy.sh index 823671c7d9..67478acb8b 100755 --- a/deploy/deploy.sh +++ b/deploy/deploy.sh @@ -46,12 +46,12 @@ fi pgbackrest_aws_s3_key=$(awsKeySecret "aws-s3-key") pgbackrest_aws_s3_key_secret=$(awsKeySecret "aws-s3-key-secret") -$PGO_CMD --namespace=$PGO_OPERATOR_NAMESPACE create secret generic pgo-backrest-repo-config \ - --from-file=config=${PGO_CONF_DIR}/pgo-backrest-repo/config \ - --from-file=sshd_config=${PGO_CONF_DIR}/pgo-backrest-repo/sshd_config \ - --from-file=aws-s3-ca.crt=${PGO_CONF_DIR}/pgo-backrest-repo/aws-s3-ca.crt \ - --from-literal=aws-s3-key="${pgbackrest_aws_s3_key}" \ - --from-literal=aws-s3-key-secret="${pgbackrest_aws_s3_key_secret}" +if [[ ! -z $pgbackrest_aws_s3_key ]] || [[ ! -z $pgbackrest_aws_s3_key_secret ]] +then + $PGO_CMD --namespace=$PGO_OPERATOR_NAMESPACE create secret generic pgo-backrest-repo-config \ + --from-literal=aws-s3-key="${pgbackrest_aws_s3_key}" \ + --from-literal=aws-s3-key-secret="${pgbackrest_aws_s3_key_secret}" +fi # # credentials for pgo-apiserver TLS REST API diff --git a/docs/content/installation/other/operator-hub.md b/docs/content/installation/other/operator-hub.md index b610ee2664..caebfb3a95 100644 --- a/docs/content/installation/other/operator-hub.md +++ b/docs/content/installation/other/operator-hub.md @@ -15,44 +15,14 @@ that is available in OperatorHub.io. ## Before You Begin -There are a few manual steps that the cluster administrator must perform prior to installing the PostgreSQL Operator. -At the very least, it must be provided with an initial configuration. +There are some optional Secrets you can add before installing the PostgreSQL Operator into your cluster. -First, make sure OLM and the OperatorHub.io catalog are installed by running -`kubectl get CatalogSources --all-namespaces`. You should see something similar to the following: +### Secrets (optional) -``` -NAMESPACE NAME DISPLAY TYPE PUBLISHER -olm operatorhubio-catalog Community Operators grpc OperatorHub.io -``` - -Take note of the name and namespace above, you will need them later on. - -Next, select a namespace in which to install the PostgreSQL Operator. PostgreSQL clusters will also be deployed here. -If it does not exist, create it now. - -``` -export PGO_OPERATOR_NAMESPACE=pgo -kubectl create namespace "$PGO_OPERATOR_NAMESPACE" -``` - -Next, clone the PostgreSQL Operator repository locally. - -``` -git clone -b v{{< param operatorVersion >}} https://github.com/CrunchyData/postgres-operator.git -cd postgres-operator -``` - -### Secrets - -Configure pgBackRest for your environment. If you do not plan to use AWS S3 to store backups, you can omit -the `aws-s3` keys below. +If you plan to use AWS S3 to store backups and would like to have the keys available for every backup, you can create a Secret as described below: ``` kubectl -n "$PGO_OPERATOR_NAMESPACE" create secret generic pgo-backrest-repo-config \ - --from-file=./installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/config \ - --from-file=./installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/sshd_config \ - --from-file=./installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/aws-s3-ca.crt \ --from-literal=aws-s3-key="" \ --from-literal=aws-s3-key-secret="" ``` @@ -69,9 +39,6 @@ kubectl -n "$PGO_OPERATOR_NAMESPACE" create secret tls pgo.tls \ --key=/path/to/server.key ``` -Once these resources are in place, the PostgreSQL Operator can be installed into the cluster. - - ## Installation Create an `OperatorGroup` and a `Subscription` in your chosen namespace. diff --git a/installers/ansible/roles/pgo-operator/tasks/main.yml b/installers/ansible/roles/pgo-operator/tasks/main.yml index c9fc36e6a0..92d14725ca 100644 --- a/installers/ansible/roles/pgo-operator/tasks/main.yml +++ b/installers/ansible/roles/pgo-operator/tasks/main.yml @@ -106,7 +106,7 @@ when: pgorole_pgoadmin_result.rc == 1 - name: PGO Service Account - when: + when: - create_rbac|bool tags: - install @@ -128,7 +128,7 @@ when: pgo_service_account_result.rc == 1 - name: Cluster RBAC (namespace_mode 'dynamic') - when: + when: - create_rbac|bool - namespace_mode == "dynamic" tags: @@ -151,7 +151,7 @@ when: cluster_rbac_result.rc == 1 - name: Cluster RBAC (namespace_mode 'readonly') - when: + when: - create_rbac|bool - namespace_mode == "readonly" tags: @@ -179,7 +179,7 @@ tags: - install - update - when: + when: - create_rbac|bool - namespace_mode == "disabled" @@ -266,13 +266,13 @@ - name: Create PGO BackRest Repo Secret command: | {{ kubectl_or_oc }} create secret generic pgo-backrest-repo-config \ - --from-file=config='{{ role_path }}/files/pgo-backrest-repo/config' \ - --from-file=sshd_config='{{ role_path }}/files/pgo-backrest-repo/sshd_config' \ - --from-file=aws-s3-ca.crt='{{ role_path }}/files/pgo-backrest-repo/aws-s3-ca.crt' \ --from-literal=aws-s3-key='{{ backrest_aws_s3_key }}' \ --from-literal=aws-s3-key-secret='{{ backrest_aws_s3_secret }}' \ -n {{ pgo_operator_namespace }} - when: pgo_backrest_repo_config_result.rc == 1 + when: + - pgo_backrest_repo_config_result.rc == 1 + - (backrest_aws_s3_key | default('') != '') or + (backrest_aws_s3_secret | default('') != '') - name: PGO API Secret tags: @@ -307,7 +307,7 @@ shell: "{{ kubectl_or_oc }} get configmap pgo-config -n {{ pgo_operator_namespace }}" register: pgo_config_result failed_when: false - + - name: Create PGO ConfigMap command: | {{ kubectl_or_oc }} create configmap pgo-config \ @@ -403,8 +403,8 @@ shell: "{{ kubectl_or_oc }} get -f {{ output_dir }}/pgo-client.json" register: pgo_client_json_result failed_when: false - + - name: Create PGO-Client deployment command: | {{ kubectl_or_oc }} create --filename='{{ output_dir }}/pgo-client.json' - when: pgo_client_json_result.rc == 1 \ No newline at end of file + when: pgo_client_json_result.rc == 1 diff --git a/installers/olm/description.openshift.md b/installers/olm/description.openshift.md index 76be0028da..6b1e79184b 100644 --- a/installers/olm/description.openshift.md +++ b/installers/olm/description.openshift.md @@ -56,20 +56,12 @@ edit `conf/postgres-operator/pgo.yaml` and set `DisableFSGroup` to `true`. [Security Context Constraint]: https://docs.openshift.com/container-platform/latest/authentication/managing-security-context-constraints.html -### Secrets +### Secrets (optional) -Configure pgBackRest for your environment. If you do not plan to use AWS S3 to store backups, you can omit -the `aws-s3` keys below. +If you plan to use AWS S3 to store backups, you can configure your environment to automatically provide your AWS S3 credentials to all newly created PostgreSQL clusters: ``` -curl https://raw.githubusercontent.com/CrunchyData/postgres-operator/v${PGO_VERSION}/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/config > config -curl https://raw.githubusercontent.com/CrunchyData/postgres-operator/v${PGO_VERSION}/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/sshd_config > sshd_config -curl https://raw.githubusercontent.com/CrunchyData/postgres-operator/v${PGO_VERSION}/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/aws-s3-ca.crt > aws-s3-ca.crt - oc -n "$PGO_OPERATOR_NAMESPACE" create secret generic pgo-backrest-repo-config \ - --from-file=./config \ - --from-file=./sshd_config \ - --from-file=./aws-s3-ca.crt \ --from-literal=aws-s3-key="" \ --from-literal=aws-s3-key-secret="" ``` diff --git a/installers/olm/description.upstream.md b/installers/olm/description.upstream.md index 7d1dcce69d..9851ee914c 100644 --- a/installers/olm/description.upstream.md +++ b/installers/olm/description.upstream.md @@ -49,20 +49,12 @@ export PGO_OPERATOR_NAMESPACE=pgo kubectl create namespace "$PGO_OPERATOR_NAMESPACE" ``` -### Secrets +### Secrets (optional) -Configure pgBackRest for your environment. If you do not plan to use AWS S3 to store backups, you can omit -the `aws-s3` keys below. +If you plan to use AWS S3 to store backups, you can configure your environment to automatically provide your AWS S3 credentials to all newly created PostgreSQL clusters: ``` -curl https://raw.githubusercontent.com/CrunchyData/postgres-operator/v${PGO_VERSION}/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/config > config -curl https://raw.githubusercontent.com/CrunchyData/postgres-operator/v${PGO_VERSION}/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/sshd_config > sshd_config -curl https://raw.githubusercontent.com/CrunchyData/postgres-operator/v${PGO_VERSION}/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/aws-s3-ca.crt > aws-s3-ca.crt - kubectl -n "$PGO_OPERATOR_NAMESPACE" create secret generic pgo-backrest-repo-config \ - --from-file=./config \ - --from-file=./sshd_config \ - --from-file=./aws-s3-ca.crt \ --from-literal=aws-s3-key="" \ --from-literal=aws-s3-key-secret="" ``` diff --git a/internal/config/secrets.go b/internal/config/secrets.go new file mode 100644 index 0000000000..2cc2b5ba1b --- /dev/null +++ b/internal/config/secrets.go @@ -0,0 +1,18 @@ +package config + +/* + Copyright 2020 Crunchy Data Solutions, Inc. + 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. +*/ + +const SecretOperatorBackrestRepoConfig = "pgo-backrest-repo-config" diff --git a/internal/operator/common.go b/internal/operator/common.go index 2d4360deb7..faeb64fcba 100644 --- a/internal/operator/common.go +++ b/internal/operator/common.go @@ -17,8 +17,11 @@ package operator import ( "bytes" + "context" "encoding/json" + "io/ioutil" "os" + "path" "strings" "github.com/crunchydata/postgres-operator/internal/config" @@ -27,10 +30,15 @@ import ( log "github.com/sirupsen/logrus" v1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" ) const ( + // defaultBackrestRepoConfigPath contains the default configuration that are used + // to set up a pgBackRest repository + defaultBackrestRepoConfigPath = "/default-pgo-backrest-repo/" // defaultRegistry is the default registry to pull the container images from defaultRegistry = "registry.developers.crunchydata.com/crunchydata" ) @@ -62,6 +70,10 @@ type containerResourcesTemplateFields struct { RequestsMemory, RequestsCPU string } +// defaultBackrestRepoConfigKeys are the default keys expected to be in the +// pgBackRest repo config secret +var defaultBackrestRepoConfigKeys = []string{"config", "sshd_config", "aws-s3-ca.crt"} + func Initialize(clientset kubernetes.Interface) { tmp := os.Getenv("CRUNCHY_DEBUG") @@ -89,16 +101,15 @@ func Initialize(clientset kubernetes.Interface) { os.Exit(2) } - var err error - - err = Pgo.GetConfig(clientset, PgoNamespace) - if err != nil { + if err := Pgo.GetConfig(clientset, PgoNamespace); err != nil { log.Error(err) - log.Error("pgo-config files and templates did not load") - os.Exit(2) + log.Fatal("pgo-config files and templates did not load") } - log.Printf("PrimaryStorage=%v\n", Pgo.Storage["storage1"]) + // initialize the general pgBackRest secret + if err := initializeOperatorBackrestSecret(clientset, PgoNamespace); err != nil { + log.Fatal(err) + } if Pgo.Cluster.CCPImagePrefix == "" { log.Debugf("pgo.yaml CCPImagePrefix not set, using default %q", defaultRegistry) @@ -360,6 +371,71 @@ func initializeControllerWorkerCounts() { } } +// initializeOperatorBackrestSecret ensures the generic pgBackRest configuration +// is available +func initializeOperatorBackrestSecret(clientset kubernetes.Interface, namespace string) error { + var isNew, isModified bool + + ctx := context.TODO() + + // determine if the Secret already exists + secret, err := clientset. + CoreV1().Secrets(namespace). + Get(ctx, config.SecretOperatorBackrestRepoConfig, metav1.GetOptions{}) + + // if there is a true error, return. Otherwise, initialize a new Secret + if err != nil { + if !kerrors.IsNotFound(err) { + return err + } + + secret = &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: config.SecretOperatorBackrestRepoConfig, + }, + Data: map[string][]byte{}, + } + isNew = true + } + + // set any missing defaults + for _, filename := range defaultBackrestRepoConfigKeys { + // skip if there is already content + if len(secret.Data[filename]) != 0 { + continue + } + + file := path.Join(defaultBackrestRepoConfigPath, filename) + + // if we can't read the contents of the file for whatever reason, warn, + // but continue + // otherwise, update the entry in the Secret + if contents, err := ioutil.ReadFile(file); err != nil { + log.Warn(err) + continue + } else { + secret.Data[filename] = contents + } + + isModified = true + } + + // do not make any updates if the secret is not modified at all + if !isModified { + return nil + } + + // make the API calls based on if we are creating or updating + if isNew { + _, err := clientset.CoreV1().Secrets(namespace).Create(ctx, secret, metav1.CreateOptions{}) + return err + } + + _, err = clientset.CoreV1().Secrets(namespace).Update(ctx, secret, metav1.UpdateOptions{}) + + return err +} + // SetupNamespaces is responsible for the initial namespace configuration for the Operator // install. This includes setting the proper namespace operating mode, creating and/or updating // namespaces as needed (or as permitted by the current operator mode), and returning a valid list diff --git a/internal/util/cluster.go b/internal/util/cluster.go index 185bc04035..03fbb3c69b 100644 --- a/internal/util/cluster.go +++ b/internal/util/cluster.go @@ -152,7 +152,7 @@ func CreateBackrestRepoSecrets(clientset kubernetes.Interface, // SSHD(...?) and possible S3 credentials configs, configErr := clientset. CoreV1().Secrets(backrestRepoConfig.OperatorNamespace). - Get(ctx, "pgo-backrest-repo-config", metav1.GetOptions{}) + Get(ctx, config.SecretOperatorBackrestRepoConfig, metav1.GetOptions{}) if configErr != nil { log.Error(configErr) From cef395600f847aa836beea52151a4a72f051c0b6 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 17 Nov 2020 21:48:16 -0500 Subject: [PATCH 016/276] Provide clarity on retrieving credentials for standbys The `pgo show user` command has a `--show-system-accounts` flag for retrieving the credentials of system accounts (e.g. superuser, replication users, etc.), which can be helpful when setting up standby clusters. Issue: #2053 --- .../high-availability/multi-cluster-kubernetes.md | 11 +++++++++++ docs/content/pgo-client/common-tasks.md | 8 ++++++++ 2 files changed, 19 insertions(+) diff --git a/docs/content/architecture/high-availability/multi-cluster-kubernetes.md b/docs/content/architecture/high-availability/multi-cluster-kubernetes.md index c6043adba4..f2be1e03c5 100644 --- a/docs/content/architecture/high-availability/multi-cluster-kubernetes.md +++ b/docs/content/architecture/high-availability/multi-cluster-kubernetes.md @@ -93,6 +93,14 @@ that matches that of the active cluster it is replicating. - `--pgbackrest-s3-endpoint`: The S3 endpoint to use - `--pgbackrest-s3-region`: The S3 region to use +If you do not want to set the user credentials, you can retrieve them at a later +time by using the [`pgo show user`]({{< relref "/pgo-client/reference/pgo_show_user.md" >}}) +command with the `--show-system-accounts` flag, e.g. + +``` +pgo show user --show-system-accounts hippo +``` + With respect to the credentials, it should be noted that when the standby cluster is being created within the same Kubernetes cluster AND it has access to the Kubernetes Secret created for the active cluster, one can use the @@ -182,6 +190,9 @@ pgo create cluster hippo-standby --standby --pgbouncer --replica-count=2 \ --password=opensourcehippo ``` +(If you are unsure of your credentials, you can use +`pgo show user hippo --show-system-accounts` to retrieve them). + Note the use of the `--pgbackrest-repo-path` flag as it points to the name of the pgBackRest repository that is used for the original `hippo` cluster. diff --git a/docs/content/pgo-client/common-tasks.md b/docs/content/pgo-client/common-tasks.md index 50bdd46e72..33396c1214 100644 --- a/docs/content/pgo-client/common-tasks.md +++ b/docs/content/pgo-client/common-tasks.md @@ -1298,6 +1298,14 @@ pgo create cluster hippo-standby --standby --replica-count=2 \ --password=opensourcehippo ``` +If you are unsure of your user credentials form the original `hippo` cluster, +you can retrieve them using the [`pgo show user`]({{< relref "/pgo-client/reference/pgo_show_user.md" >}}) +command with the `--show-system-accounts` flag: + +``` +pgo show user hippo --show-system-accounts +``` + The standby cluster will take a few moments to bootstrap, but it is now set up! ### Promoting a Standby Cluster From 0d0c6a388b66d3fad794b14533fb3a6416ffd5d5 Mon Sep 17 00:00:00 2001 From: Chris Bandy Date: Tue, 17 Nov 2020 15:56:18 -0600 Subject: [PATCH 017/276] Use GitHub Actions to run golangci-lint on new Go code - https://github.com/features/actions - https://docs.github.com/en/free-pro-team@latest/actions --- .github/workflows/lint.yaml | 15 +++++++++++++++ .golangci.yaml | 16 ++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 .github/workflows/lint.yaml create mode 100644 .golangci.yaml diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 0000000000..29e173c4fa --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,15 @@ +on: + pull_request: + branches: + - master + +jobs: + golangci-lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: golangci/golangci-lint-action@v2 + with: + version: v1.32 + args: --timeout=5m + only-new-issues: true diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000000..c8ac7c76ed --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,16 @@ +# https://golangci-lint.run/usage/configuration/ + +linters: + disable: + - scopelint + enable: + - gosimple + - misspell + presets: + - bugs + - format + - unused + +run: + skip-dirs: + - pkg/generated From a08ac1d7e8d6d63edbc004fe1633c32265f20cd1 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Fri, 20 Nov 2020 13:35:37 -0500 Subject: [PATCH 018/276] Add command defaults to some of the examples Given the variable name `$PGO_CMD` can involve a lot of context and given that some of the examples may be run without the precursor "envs" executable being run, this adds a sane default of `kubectl` to some of the example scripts, while also indicating what are acceptable values, should one introspect the scripts. Issue: #1928 --- examples/create-by-resource/run.sh | 2 ++ examples/custom-config/create.sh | 7 ++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/examples/create-by-resource/run.sh b/examples/create-by-resource/run.sh index ea034a4fe2..e6940ead12 100755 --- a/examples/create-by-resource/run.sh +++ b/examples/create-by-resource/run.sh @@ -18,6 +18,8 @@ ######### DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +# PGO_CMD should either be "kubectl" or "oc" -- defaulting to kubectl +PGO_CMD=${PGO_CMD:-kubectl} # A namespace that exists in NAMESPACE env var - see examples/envs.sh export NS=pgouser1 diff --git a/examples/custom-config/create.sh b/examples/custom-config/create.sh index b0599f1b37..df6c701f2a 100755 --- a/examples/custom-config/create.sh +++ b/examples/custom-config/create.sh @@ -28,11 +28,8 @@ function echo_info() { DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -#Error if PGO_CMD not set -if [[ -z ${PGO_CMD} ]] -then - echo_err "PGO_CMD is not set." -fi +# PGO_CMD should either be "kubectl" or "oc" -- defaulting to kubectl +PGO_CMD=${PGO_CMD:-kubectl} #Error is PGO_NAMESPACE not set if [[ -z ${PGO_NAMESPACE} ]] From 2f32be89b2cd785640bc833d7c709d4a99b0274a Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Fri, 27 Nov 2020 11:54:40 -0500 Subject: [PATCH 019/276] Fix crash in cluster shutdown logic The Operator would crash if a shutdown was issued but there was no in the cluster. Issue: [ch9825] Issue: #2073 --- internal/operator/cluster/clusterlogic.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/operator/cluster/clusterlogic.go b/internal/operator/cluster/clusterlogic.go index dd8a9e6827..7b78d66f00 100644 --- a/internal/operator/cluster/clusterlogic.go +++ b/internal/operator/cluster/clusterlogic.go @@ -630,7 +630,10 @@ func ShutdownCluster(clientset kubeapi.Interface, cluster crv1.Pgcluster) error return err } - if len(pods.Items) > 1 { + if len(pods.Items) == 0 { + return fmt.Errorf("Cluster Operator: Could not find primary pod for shutdown of "+ + "cluster %s", cluster.Name) + } else if len(pods.Items) > 1 { return fmt.Errorf("Cluster Operator: Invalid number of primary pods (%d) found when "+ "shutting down cluster %s", len(pods.Items), cluster.Name) } From 7c22d5ef6929da44cbab21a2feb6e8b4311b0dd2 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 29 Nov 2020 14:56:01 -0500 Subject: [PATCH 020/276] Add TLS support for pgBouncer Since 2b05f01b, the Postgres Operator has supported configuration of TLS connections for PostgreSQL clusters. However, connections made to pgBouncer did not support TLS directly, though one could do some heavy work to the template files to bring about this support. This introduces the ability to directly configure pgBouncer instances with TLS, with the following preconditions: - TLS MUST be enabled within the PostgreSQL cluster - pgBouncer and the PostgreSQL share the same certificate authority (CA) bundle When TLS is enabled, connections are facilitated with the following default rules: - All connections to pgBouncer MUST be over TLS. Effectively, this is "TLS only" if connecting via pgBouncer. - Connections coming into pgBouncer have a PGSSLMODE of "require" - Connections going into PostgreSQL a PGSSLMODE of "verify-ca" This adds an attribute to the pgcluster Spec in the "pgBouncer" section called "tlsSecret", which will store the name of the TLS secret to use for pgBouncer. One can also enable TLS for pgBouncer in the following ways: - `pgo create cluster --pgbouncer-tls-secret` if `--pgbouncer` is set - `pgo create pgbouncer --tls-secret` Issue: [ch9777] --- cmd/pgo/cmd/cluster.go | 1 + cmd/pgo/cmd/create.go | 9 +++ cmd/pgo/cmd/pgbouncer.go | 1 + docs/content/custom-resources/_index.md | 1 + docs/content/pgo-client/common-tasks.md | 17 +---- .../reference/pgo_create_cluster.md | 5 +- .../reference/pgo_create_pgbouncer.md | 5 +- docs/content/tutorial/pgbouncer.md | 71 +++++++++++++++++++ examples/pgo-bash-completion | 4 -- .../files/pgo-configs/pgbouncer-template.json | 39 +++++++++- .../files/pgo-configs/pgbouncer.ini | 8 +++ .../files/pgo-configs/pgbouncer_hba.conf | 4 ++ .../apiserver/clusterservice/clusterimpl.go | 31 +++++++- .../pgbouncerservice/pgbouncerimpl.go | 46 +++++++++++- internal/operator/cluster/pgbouncer.go | 37 ++++++++-- internal/operator/cluster/pgbouncer_test.go | 52 ++++++++++++++ pkg/apis/crunchydata.com/v1/cluster.go | 5 ++ pkg/apiservermsgs/clustermsgs.go | 6 +- pkg/apiservermsgs/pgbouncermsgs.go | 3 + 19 files changed, 309 insertions(+), 36 deletions(-) diff --git a/cmd/pgo/cmd/cluster.go b/cmd/pgo/cmd/cluster.go index 12179d8fcf..a125bc2b5b 100644 --- a/cmd/pgo/cmd/cluster.go +++ b/cmd/pgo/cmd/cluster.go @@ -327,6 +327,7 @@ func createCluster(args []string, ns string, createClusterCmd *cobra.Command) { r.PgBouncerMemoryRequest = PgBouncerMemoryRequest r.PgBouncerMemoryLimit = PgBouncerMemoryLimit r.PgBouncerReplicas = PgBouncerReplicas + r.PgBouncerTLSSecret = PgBouncerTLSSecret // determine if the user wants to create tablespaces as part of this request, // and if so, set the values r.Tablespaces = getTablespaces(Tablespaces) diff --git a/cmd/pgo/cmd/create.go b/cmd/pgo/cmd/create.go index 57e4e77eb6..25dd1119b9 100644 --- a/cmd/pgo/cmd/create.go +++ b/cmd/pgo/cmd/create.go @@ -122,6 +122,9 @@ var PasswordReplication string // variables used for setting up TLS-enabled PostgreSQL clusters var ( + // PgBouncerTLSSecret is the name of the secret that contains the + // TLS information for enabling TLS for pgBouncer + PgBouncerTLSSecret string // TLSOnly indicates that only TLS connections will be accepted for a // PostgreSQL cluster TLSOnly bool @@ -435,6 +438,9 @@ func init() { createClusterCmd.Flags().StringVar(&PgBouncerMemoryLimit, "pgbouncer-memory-limit", "", "Set the amount of memory to limit for "+ "pgBouncer.") createClusterCmd.Flags().Int32Var(&PgBouncerReplicas, "pgbouncer-replicas", 0, "Set the total number of pgBouncer instances to deploy. If not set, defaults to 1.") + createClusterCmd.Flags().StringVar(&PgBouncerTLSSecret, "pgbouncer-tls-secret", "", "The name of the secret "+ + "that contains the TLS keypair to use for enabling pgBouncer to accept TLS connections. "+ + "Must also set server-tls-secret and server-ca-secret.") createClusterCmd.Flags().StringVarP(&ReplicaStorageConfig, "replica-storage-config", "", "", "The name of a Storage config in pgo.yaml to use for the cluster replica storage.") createClusterCmd.Flags().StringVarP(&PodAntiAffinity, "pod-anti-affinity", "", "", "Specifies the type of anti-affinity that should be utilized when applying "+ @@ -504,6 +510,9 @@ func init() { "pgBouncer.") createPgbouncerCmd.Flags().Int32Var(&PgBouncerReplicas, "replicas", 0, "Set the total number of pgBouncer instances to deploy. If not set, defaults to 1.") createPgbouncerCmd.Flags().StringVarP(&Selector, "selector", "s", "", "The selector to use for cluster filtering.") + createPgbouncerCmd.Flags().StringVar(&PgBouncerTLSSecret, "tls-secret", "", "The name of the secret "+ + "that contains the TLS keypair to use for enabling pgBouncer to accept TLS connections. "+ + "The PostgreSQL cluster must have TLS enabled.") // "pgo create pgouser" flags createPgouserCmd.Flags().BoolVarP(&AllNamespaces, "all-namespaces", "", false, "specifies this user will have access to all namespaces.") diff --git a/cmd/pgo/cmd/pgbouncer.go b/cmd/pgo/cmd/pgbouncer.go index d787b1ebbe..9450623bf1 100644 --- a/cmd/pgo/cmd/pgbouncer.go +++ b/cmd/pgo/cmd/pgbouncer.go @@ -68,6 +68,7 @@ func createPgbouncer(args []string, ns string) { Namespace: ns, Replicas: PgBouncerReplicas, Selector: Selector, + TLSSecret: PgBouncerTLSSecret, } if err := util.ValidateQuantity(request.CPURequest, "cpu"); err != nil { diff --git a/docs/content/custom-resources/_index.md b/docs/content/custom-resources/_index.md index f4384e311e..9869a29bb1 100644 --- a/docs/content/custom-resources/_index.md +++ b/docs/content/custom-resources/_index.md @@ -784,6 +784,7 @@ a PostgreSQL cluster to help with failover scenarios too. | Limits | `create`, `update` | Specify the container resource limits that the pgBouncer Pods should use. Follows the [Kubernetes definitions of resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | | Replicas | `create`, `update` | The number of pgBouncer instances to deploy. Must be set to at least `1` to deploy pgBouncer. Setting to `0` removes an existing pgBouncer deployment for the PostgreSQL cluster. | | Resources | `create`, `update` | Specify the container resource requests that the pgBouncer Pods should use. Follows the [Kubernetes definitions of resource requests](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | +| TLSSecret | `create` | A reference to the name of a Kubernetes TLS Secret that contains a keypair that is used for the pgBouncer instance to identify itself and perform TLS communications with PostgreSQL clients. Must be used with the parent Spec `TLSSecret` and `CASecret`. | ##### Annotations Specification diff --git a/docs/content/pgo-client/common-tasks.md b/docs/content/pgo-client/common-tasks.md index 33396c1214..8eefb412c2 100644 --- a/docs/content/pgo-client/common-tasks.md +++ b/docs/content/pgo-client/common-tasks.md @@ -1485,22 +1485,7 @@ You can view policies as following: ### Connection Pooling via pgBouncer -To add a pgbouncer Deployment to your Postgres cluster, enter: - - pgo create cluster hacluster --pgbouncer -n pgouser1 - -You can add pgbouncer after a Postgres cluster is created as follows: - - pgo create pgbouncer hacluster - pgo create pgbouncer --selector=name=hacluster - -You can also specify a pgbouncer password as follows: - - pgo create cluster hacluster --pgbouncer --pgbouncer-pass=somepass -n pgouser1 - -You can remove a pgbouncer from a cluster as follows: - - pgo delete pgbouncer hacluster -n pgouser1 +Please see the [tutorial on pgBouncer]({{< relref "tutorial/pgbouncer.md" >}}). ### Query Analysis via pgBadger diff --git a/docs/content/pgo-client/reference/pgo_create_cluster.md b/docs/content/pgo-client/reference/pgo_create_cluster.md index 265b3c5517..7a8661845d 100644 --- a/docs/content/pgo-client/reference/pgo_create_cluster.md +++ b/docs/content/pgo-client/reference/pgo_create_cluster.md @@ -79,6 +79,7 @@ pgo create cluster [flags] --pgbouncer-memory string Set the amount of memory to request for pgBouncer. Defaults to server value (24Mi). --pgbouncer-memory-limit string Set the amount of memory to limit for pgBouncer. --pgbouncer-replicas int32 Set the total number of pgBouncer instances to deploy. If not set, defaults to 1. + --pgbouncer-tls-secret string The name of the secret that contains the TLS keypair to use for enabling pgBouncer to accept TLS connections. Must also set server-tls-secret and server-ca-secret. --pgo-image-prefix string The PGOImagePrefix to use for cluster creation. If specified, overrides the global configuration. --pod-anti-affinity string Specifies the type of anti-affinity that should be utilized when applying default pod anti-affinity rules to PG clusters (default "preferred") --pod-anti-affinity-pgbackrest string Set the Pod anti-affinity rules specifically for the pgBackRest repository. Defaults to the default cluster pod anti-affinity (i.e. "preferred"), or the value set by --pod-anti-affinity @@ -116,7 +117,7 @@ pgo create cluster [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -130,4 +131,4 @@ pgo create cluster [flags] * [pgo create](/pgo-client/reference/pgo_create/) - Create a Postgres Operator resource -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 22-Nov-2020 diff --git a/docs/content/pgo-client/reference/pgo_create_pgbouncer.md b/docs/content/pgo-client/reference/pgo_create_pgbouncer.md index ad406e60e0..156820f023 100644 --- a/docs/content/pgo-client/reference/pgo_create_pgbouncer.md +++ b/docs/content/pgo-client/reference/pgo_create_pgbouncer.md @@ -25,12 +25,13 @@ pgo create pgbouncer [flags] --memory-limit string Set the amount of memory to limit for pgBouncer. --replicas int32 Set the total number of pgBouncer instances to deploy. If not set, defaults to 1. -s, --selector string The selector to use for cluster filtering. + --tls-secret string The name of the secret that contains the TLS keypair to use for enabling pgBouncer to accept TLS connections. The PostgreSQL cluster must have TLS enabled. ``` ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -44,4 +45,4 @@ pgo create pgbouncer [flags] * [pgo create](/pgo-client/reference/pgo_create/) - Create a Postgres Operator resource -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 22-Nov-2020 diff --git a/docs/content/tutorial/pgbouncer.md b/docs/content/tutorial/pgbouncer.md index 0349ff1eaf..43c2534996 100644 --- a/docs/content/tutorial/pgbouncer.md +++ b/docs/content/tutorial/pgbouncer.md @@ -130,6 +130,77 @@ SHOW stats; Success, you have connected to pgBouncer! +## Setup pgBouncer with TLS + +Similarly to how you can [setup TLS for PostgreSQL]({{< relref "tutorial/tls.md" >}}), you can set up TLS connections for pgBouncer. To do this, the PostgreSQL Operator takes the following steps: + +- Ensuring TLS communication between a client (e.g. `psql`, your application, etc.) and pgBouncer +- Ensuring TLS communication between pgBouncer and PostgreSQL + +When TLS is enabled, the PostgreSQL Operator configures pgBouncer to require each client to use TLS to communicate with pgBouncer. Additionally, the PostgreSQL Operator requires that pgBouncer and the PostgreSQL cluster share the same certificate authority (CA) bundle, which allows for pgBouncer to communicate with the PostgreSQL cluster using PostgreSQL's [`verify-ca` SSL mode](https://www.postgresql.org/docs/current/libpq-ssl.html#LIBPQ-SSL-PROTECTION). + +The below guide will show you how set up TLS for pgBouncer. + +### Prerequisites + +In order to set up TLS connections for pgBouncer, you must first [enable TLS on your PostgreSQL cluster]({{< relref "tutorial/tls.md" >}}). + +For the purposes of this exercise, we will re-use the Secret TLS keypair `hippo-tls-keypair` that was created for the PostgreSQL server. This is only being done for convenience: you can substitute `hippo-tls-keypair` with a different TLS key pair as long as it can be verified by the certificate authority (CA) that you selected for your PostgreSQL cluster. Recall that the certificate authority (CA) bundle is stored in a Secret named `postgresql-ca`. + +### Create pgBouncer with TLS + +Knowing that our TLS key pair is stored in a Secret called `hippo-tls-keypair`, you can setup pgBouncer with TLS using the following command: + +``` +pgo create pgbouncer hippo --tls-secret=hippo-tls-keypair +``` + +And that's it! So long as the prerequisites are satisfied, this will create a pgBouncer instance that is TLS enabled. + +Don't believe it? Try logging in. First, ensure you have a port-forward from pgBouncer to your host machine: + +``` +kubectl -n pgo port-forward svc/hippo-pgbouncer 5432:5432 +``` + +Then, connect to the pgBouncer instances: + +``` +PGPASSWORD=securerandomlygeneratedpassword psql -h localhost -p 5432 -U testuser hippo +``` + +You should see something similar to this: + +``` +psql (12.5) +SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off) +Type "help" for help. + +hippo=> +``` + +Still don't believe it? You can verify your connection using the PostgreSQL `get_backend_pid()` function and the [`pg_stat_ssl`](https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-SSL-VIEW) monitoring view: + +``` +hippo=> SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid(); + pid | ssl | version | cipher | bits | compression | client_dn | client_serial | issuer_dn +-------+-----+---------+------------------------+------+-------------+-----------+---------------+----------- + 15653 | t | TLSv1.3 | TLS_AES_256_GCM_SHA384 | 256 | f | | | +(1 row) +``` + +### Create a PostgreSQL cluster with pgBouncer and TLS + +Want to create a PostgreSQL cluster with pgBouncer with TLS enabled? You can with the [`pgo create cluster`]({{< relref "pgo-client/reference/pgo_create_cluster.md" >}}) command and using the `--pgbouncer-tls-secret` flag. Using the same Secrets that were created in the [creating a PostgreSQL cluster with TLS]({{ relref "tutorial/tls.md" }}) tutorial, you can create a PostgreSQL cluster with pgBouncer and TLS with the following command: + +``` +pgo create cluster hippo \ + --server-ca-secret=postgresql-ca \ + --server-tls-secret=hippo-tls-keypair \ + --pgbouncer \ + --pgbouncer-tls-secret=hippo-tls-keypair +``` + ## Customize CPU / Memory for pgBouncer ### Provisioning diff --git a/examples/pgo-bash-completion b/examples/pgo-bash-completion index 70271ccf0c..6c89ce0b37 100644 --- a/examples/pgo-bash-completion +++ b/examples/pgo-bash-completion @@ -383,8 +383,6 @@ _pgo_create_cluster() local_nonpersistent_flags+=("--pgbadger") flags+=("--pgbouncer") local_nonpersistent_flags+=("--pgbouncer") - flags+=("--pgbouncer-pass=") - local_nonpersistent_flags+=("--pgbouncer-pass=") flags+=("--policies=") two_word_flags+=("-z") local_nonpersistent_flags+=("--policies=") @@ -454,8 +452,6 @@ _pgo_create_pgbouncer() flags_with_completion=() flags_completion=() - flags+=("--pgbouncer-pass=") - local_nonpersistent_flags+=("--pgbouncer-pass=") flags+=("--selector=") two_word_flags+=("-s") local_nonpersistent_flags+=("--selector=") diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer-template.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer-template.json index 38202a7464..9e88f4bbdb 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer-template.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer-template.json @@ -39,6 +39,11 @@ }, "spec": { "serviceAccountName": "pgo-default", + {{ if not .DisableFSGroup }} + "securityContext": { + "fsGroup": 2 + }, + {{ end }} "containers": [{ "name": "pgbouncer", "image": "{{.CCPImagePrefix}}/crunchy-pgbouncer:{{.CCPImageTag}}", @@ -59,13 +64,41 @@ "name": "PG_PRIMARY_SERVICE_NAME", "value": "{{.PrimaryServiceName}}" }], - "volumeMounts": [{ + "volumeMounts": [ + {{if .TLSEnabled}} + { + "mountPath": "/pgconf/tls/pgbouncer", + "name": "tls-pgbouncer" + }, + {{ end }} + { "name": "pgbouncer-conf", "mountPath": "/pgconf/", "readOnly": false - }] + } + ] }], "volumes": [ + {{if .TLSEnabled}} + { + "name": "tls-pgbouncer", + "defaultMode": 288, + "projected": { + "sources": [ + { + "secret": { + "name": "{{.TLSSecret}}" + } + }, + { + "secret": { + "name": "{{.CASecret}}" + } + } + ] + } + }, + {{ end }} { "name": "pgbouncer-conf", "projected": { @@ -78,7 +111,7 @@ { "secret": { "name": "{{.PGBouncerSecret}}", - "defaultMode": 511 + "defaultMode": 288 } } ] diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer.ini b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer.ini index 157f9a96e1..5310692c37 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer.ini +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer.ini @@ -20,3 +20,11 @@ reserve_pool_size = 0 reserve_pool_timeout = 5 query_timeout = 0 ignore_startup_parameters = extra_float_digits +{{ if .TLSEnabled }} +client_tls_sslmode = require +client_tls_key_file = /pgconf/tls/pgbouncer/tls.key +client_tls_cert_file = /pgconf/tls/pgbouncer/tls.crt +client_tls_ca_file = /pgconf/tls/pgbouncer/ca.crt +server_tls_sslmode = verify-ca +server_tls_ca_file = /pgconf/tls/pgbouncer/ca.crt +{{ end }} diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer_hba.conf b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer_hba.conf index 824c82705e..aee753cd1a 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer_hba.conf +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer_hba.conf @@ -1 +1,5 @@ +{{ if .TLSEnabled }} +hostssl all all 0.0.0.0/0 md5 +{{ else }} host all all 0.0.0.0/0 md5 +{{ end }} diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index 8369d421ec..e099d0303f 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -1264,6 +1264,11 @@ func getClusterParams(request *msgs.CreateClusterRequest, name string, userLabel spec.PgBouncer.Resources[v1.ResourceMemory] = apiserver.Pgo.Cluster.DefaultPgBouncerResourceMemory } + // if TLS is enabled for pgBouncer, ensure the secret is specified + if request.PgBouncerTLSSecret != "" { + spec.PgBouncer.TLSSecret = request.PgBouncerTLSSecret + } + spec.PrimaryStorage, _ = apiserver.Pgo.GetStorageSpec(apiserver.Pgo.PrimaryStorage) if request.StorageConfig != "" { spec.PrimaryStorage, _ = apiserver.Pgo.GetStorageSpec(request.StorageConfig) @@ -2148,12 +2153,25 @@ func validateBackrestStorageTypeOnCreate(request *msgs.CreateClusterRequest) err func validateClusterTLS(request *msgs.CreateClusterRequest) error { ctx := context.TODO() - // if ReplicationTLSSecret is set, but neither TLSSecret nor CASecret is not - // set, then return + // if ReplicationTLSSecret is set, but neither TLSSecret nor CASecret is set + // then return if request.ReplicationTLSSecret != "" && (request.TLSSecret == "" || request.CASecret == "") { return fmt.Errorf("Both TLS secret and CA secret must be set in order to enable certificate-based authentication for replication") } + // if PgBouncerTLSSecret is set, return if: + // a) pgBouncer is not enabled OR + // b) neither TLSSecret nor CASecret is set + if request.PgBouncerTLSSecret != "" { + if !request.PgbouncerFlag { + return fmt.Errorf("pgBouncer must be enabled in order to enable TLS for pgBouncer") + } + + if request.TLSSecret == "" || request.CASecret == "" { + return fmt.Errorf("Both TLS secret and CA secret must be set in order to enable TLS for pgBouncer") + } + } + // if TLSOnly is not set and neither TLSSecret no CASecret are set, just return if !request.TLSOnly && request.TLSSecret == "" && request.CASecret == "" { return nil @@ -2192,6 +2210,15 @@ func validateClusterTLS(request *msgs.CreateClusterRequest) error { } } + // then, if set, the pgBouncer TLS secret + if request.PgBouncerTLSSecret != "" { + if _, err := apiserver.Clientset. + CoreV1().Secrets(request.Namespace). + Get(ctx, request.PgBouncerTLSSecret, metav1.GetOptions{}); err != nil { + return err + } + } + // after this, we are validated! return nil } diff --git a/internal/apiserver/pgbouncerservice/pgbouncerimpl.go b/internal/apiserver/pgbouncerservice/pgbouncerimpl.go index 6ce41ac784..85373855d6 100644 --- a/internal/apiserver/pgbouncerservice/pgbouncerimpl.go +++ b/internal/apiserver/pgbouncerservice/pgbouncerimpl.go @@ -80,7 +80,6 @@ func CreatePgbouncer(request *msgs.CreatePgbouncerRequest, ns, pgouser string) m } for _, cluster := range clusterList.Items { - // check if the current cluster is not upgraded to the deployed // Operator version. If not, do not allow the command to complete if cluster.Annotations[config.ANNOTATION_IS_UPGRADED] == config.ANNOTATIONS_FALSE { @@ -89,6 +88,13 @@ func CreatePgbouncer(request *msgs.CreatePgbouncerRequest, ns, pgouser string) m return resp } + // validate the TLS settings + if err := validateTLS(cluster, request); err != nil { + resp.Status.Code = msgs.Error + resp.Status.Msg = err.Error() + return resp + } + log.Debugf("adding pgbouncer to cluster [%s]", cluster.Name) resources := v1.ResourceList{} @@ -132,6 +138,7 @@ func CreatePgbouncer(request *msgs.CreatePgbouncerRequest, ns, pgouser string) m } cluster.Spec.PgBouncer.Resources = resources + cluster.Spec.PgBouncer.TLSSecret = request.TLSSecret // update the cluster CRD with these udpates. If there is an error if _, err := apiserver.Clientset.CrunchydataV1().Pgclusters(request.Namespace). @@ -534,3 +541,40 @@ func setPgBouncerServiceDetail(cluster crv1.Pgcluster, result *msgs.ShowPgBounce } } } + +// validateTLS validates the parameters that allow a user to enable TLS +// connections to a pgBouncer cluster. In essence, it requires both the +// TLSSecret to be set for pgBouncer as well as a CASecret/TLSSecret for the +// cluster itself +func validateTLS(cluster crv1.Pgcluster, request *msgs.CreatePgbouncerRequest) error { + ctx := context.TODO() + + // if TLSSecret is not set, well, this is valid + if request.TLSSecret == "" { + return nil + } + + // if ReplicationTLSSecret is set, but neither TLSSecret nor CASecret is not + // set, then return + if request.TLSSecret != "" && (cluster.Spec.TLS.TLSSecret == "" || cluster.Spec.TLS.CASecret == "") { + return fmt.Errorf("%s: both TLS secret and CA secret must be set on the cluster in order to enable TLS for pgBouncer", cluster.Name) + } + + // ensure the TLSSecret and CASecret for the cluster are actually present + // now check for the existence of the two secrets + // First the TLS secret + if _, err := apiserver.Clientset. + CoreV1().Secrets(cluster.Namespace). + Get(ctx, cluster.Spec.TLS.TLSSecret, metav1.GetOptions{}); err != nil { + return fmt.Errorf("%s: cannot find TLS secret for cluster: %w", cluster.Name, err) + } + + if _, err := apiserver.Clientset. + CoreV1().Secrets(cluster.Namespace). + Get(ctx, cluster.Spec.TLS.CASecret, metav1.GetOptions{}); err != nil { + return fmt.Errorf("%s: cannot find CA secret for cluster: %w", cluster.Name, err) + } + + // after this, we are validated! + return nil +} diff --git a/internal/operator/cluster/pgbouncer.go b/internal/operator/cluster/pgbouncer.go index 569783a3c8..9c70884faa 100644 --- a/internal/operator/cluster/pgbouncer.go +++ b/internal/operator/cluster/pgbouncer.go @@ -51,13 +51,20 @@ type PgbouncerPasswdFields struct { type PgbouncerConfFields struct { PG_PRIMARY_SERVICE_NAME string PG_PORT string + TLSEnabled bool +} + +type pgBouncerHBATemplateFields struct { + TLSEnabled bool } type pgBouncerTemplateFields struct { Name string + CASecret string ClusterName string CCPImagePrefix string CCPImageTag string + DisableFSGroup bool Port string PrimaryServiceName string ContainerResources string @@ -68,6 +75,8 @@ type pgBouncerTemplateFields struct { PodAntiAffinityLabelName string PodAntiAffinityLabelValue string Replicas int32 `json:",string"` + TLSEnabled bool + TLSSecret string } // pgBouncerDeploymentFormat is the name of the Kubernetes Deployment that @@ -518,7 +527,7 @@ func createPgbouncerConfigMap(clientset kubernetes.Interface, cluster *crv1.Pgcl } // generate the pgbouncer HBA file - pgbouncerHBA, err := generatePgBouncerHBA() + pgbouncerHBA, err := generatePgBouncerHBA(cluster) if err != nil { log.Error(err) @@ -565,6 +574,7 @@ func createPgBouncerDeployment(clientset kubernetes.Interface, cluster *crv1.Pgc ClusterName: cluster.Name, CCPImagePrefix: util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, operator.Pgo.Cluster.CCPImagePrefix), CCPImageTag: util.GetStandardImageTag(cluster.Spec.CCPImage, cluster.Spec.CCPImageTag), + DisableFSGroup: operator.Pgo.Cluster.DisableFSGroup, Port: cluster.Spec.Port, PGBouncerConfigMap: util.GeneratePgBouncerConfigMapName(cluster.Name), PGBouncerSecret: util.GeneratePgBouncerSecretName(cluster.Name), @@ -579,6 +589,13 @@ func createPgBouncerDeployment(clientset kubernetes.Interface, cluster *crv1.Pgc Replicas: cluster.Spec.PgBouncer.Replicas, } + // set appropriate fields if TLS is enabled + if isPgBouncerTLSEnabled(cluster) { + fields.CASecret = cluster.Spec.TLS.CASecret + fields.TLSEnabled = true + fields.TLSSecret = cluster.Spec.PgBouncer.TLSSecret + } + // For debugging purposes, put the template substitution in stdout if operator.CRUNCHY_DEBUG { config.PgbouncerTemplate.Execute(os.Stdout, fields) @@ -750,6 +767,7 @@ func generatePgBouncerConf(cluster *crv1.Pgcluster) (string, error) { fields := PgbouncerConfFields{ PG_PRIMARY_SERVICE_NAME: cluster.Name, PG_PORT: port, + TLSEnabled: isPgBouncerTLSEnabled(cluster), } // perform the substitution @@ -770,12 +788,15 @@ func generatePgBouncerConf(cluster *crv1.Pgcluster) (string, error) { // generatePgBouncerHBA generates the pgBouncer host-based authentication file // using the template that is vailable -func generatePgBouncerHBA() (string, error) { - // ...apparently this is overkill, but this is here from the legacy method - // and it seems like it's "ok" to leave it like this for now... +func generatePgBouncerHBA(cluster *crv1.Pgcluster) (string, error) { + // we may have some substitutions if this is a TLS enabled cluster + fields := pgBouncerHBATemplateFields{ + TLSEnabled: isPgBouncerTLSEnabled(cluster), + } + doc := bytes.Buffer{} - if err := config.PgbouncerHBATemplate.Execute(&doc, struct{}{}); err != nil { + if err := config.PgbouncerHBATemplate.Execute(&doc, fields); err != nil { log.Error(err) return "", err @@ -852,6 +873,12 @@ func installPgBouncer(clientset kubernetes.Interface, restconfig *rest.Config, p return nil } +// isPgBouncerTLSEnabled returns true if TLS is enabled for pgBouncer, which +// means that TLS is enabled for the PostgreSQL cluster itself +func isPgBouncerTLSEnabled(cluster *crv1.Pgcluster) bool { + return cluster.Spec.PgBouncer.TLSSecret != "" && cluster.Spec.TLS.IsTLSEnabled() +} + // makePostgresPassword creates the expected hash for a password type for a // PostgreSQL password func makePostgresPassword(passwordType pgpassword.PasswordType, password string) string { diff --git a/internal/operator/cluster/pgbouncer_test.go b/internal/operator/cluster/pgbouncer_test.go index 06ff30d8b6..b95d96828c 100644 --- a/internal/operator/cluster/pgbouncer_test.go +++ b/internal/operator/cluster/pgbouncer_test.go @@ -19,8 +19,60 @@ import ( "testing" pgpassword "github.com/crunchydata/postgres-operator/internal/postgres/password" + crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" ) +func TestIsPgBouncerTLSEnabled(t *testing.T) { + cluster := &crv1.Pgcluster{ + Spec: crv1.PgclusterSpec{ + PgBouncer: crv1.PgBouncerSpec{}, + TLS: crv1.TLSSpec{}, + }, + } + + t.Run("true", func(t *testing.T) { + cluster.Spec.PgBouncer.TLSSecret = "pgbouncer-tls" + cluster.Spec.TLS.CASecret = "ca" + cluster.Spec.TLS.TLSSecret = "postgres-tls" + + if !isPgBouncerTLSEnabled(cluster) { + t.Errorf("expected true") + } + }) + + t.Run("false", func(t *testing.T) { + t.Run("neither enabled", func(t *testing.T) { + cluster.Spec.PgBouncer.TLSSecret = "" + cluster.Spec.TLS.CASecret = "" + cluster.Spec.TLS.TLSSecret = "" + + if isPgBouncerTLSEnabled(cluster) { + t.Errorf("expected false") + } + }) + + t.Run("postgres TLS enabled only", func(t *testing.T) { + cluster.Spec.PgBouncer.TLSSecret = "" + cluster.Spec.TLS.CASecret = "ca" + cluster.Spec.TLS.TLSSecret = "postgres-tls" + + if isPgBouncerTLSEnabled(cluster) { + t.Errorf("expected false") + } + }) + + t.Run("pgbouncer TLS enabled only", func(t *testing.T) { + cluster.Spec.PgBouncer.TLSSecret = "pgbouncer-tls" + cluster.Spec.TLS.CASecret = "" + cluster.Spec.TLS.TLSSecret = "" + + if isPgBouncerTLSEnabled(cluster) { + t.Errorf("expected false") + } + }) + }) +} + func TestMakePostgresPassword(t *testing.T) { t.Run("md5", func(t *testing.T) { diff --git a/pkg/apis/crunchydata.com/v1/cluster.go b/pkg/apis/crunchydata.com/v1/cluster.go index bdc02406da..91b7f8dad8 100644 --- a/pkg/apis/crunchydata.com/v1/cluster.go +++ b/pkg/apis/crunchydata.com/v1/cluster.go @@ -254,6 +254,11 @@ type PgBouncerSpec struct { // Limits, if specified, contains the container resource limits // for any pgBouncer Deployments that are part of a PostgreSQL cluster Limits v1.ResourceList `json:"limits"` + // TLSSecret contains the name of the secret to use that contains the TLS + // keypair for pgBouncer + // This follows the Kubernetes secret format ("kubernetes.io/tls") which has + // two keys: tls.crt and tls.key + TLSSecret string `json:"tlsSecret"` } // Enabled returns true if the pgBouncer is enabled for the cluster, i.e. there diff --git a/pkg/apiservermsgs/clustermsgs.go b/pkg/apiservermsgs/clustermsgs.go index e8983613c4..69bbdafe49 100644 --- a/pkg/apiservermsgs/clustermsgs.go +++ b/pkg/apiservermsgs/clustermsgs.go @@ -87,7 +87,11 @@ type CreateClusterRequest struct { // PgBouncerReplicas represents the total number of pgBouncer pods to deploy with a // PostgreSQL cluster. Only works if PgbouncerFlag is set, and if so, it must // be at least 1. If 0 is passed in, it will automatically be set to 1 - PgBouncerReplicas int32 + PgBouncerReplicas int32 + // PgBouncerTLSSecret is the name of the Secret containing the TLS keypair + // for enabling TLS with pgBouncer. This also requires for TLSSecret and + // CASecret to be set + PgBouncerTLSSecret string CustomConfig string StorageConfig string WALStorageConfig string diff --git a/pkg/apiservermsgs/pgbouncermsgs.go b/pkg/apiservermsgs/pgbouncermsgs.go index 0feab5f15e..49669b8fe7 100644 --- a/pkg/apiservermsgs/pgbouncermsgs.go +++ b/pkg/apiservermsgs/pgbouncermsgs.go @@ -40,6 +40,9 @@ type CreatePgbouncerRequest struct { // automatically be set to 1 Replicas int32 Selector string + // TLSSecret is the name of the secret that contains the keypair required to + // deploy TLS-enabled pgBouncer + TLSSecret string } // CreatePgbouncerResponse ... From 8706c43df05498d99a757a5ecdbcfaccc6929053 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 30 Nov 2020 10:18:13 -0500 Subject: [PATCH 021/276] Catch error when generating default pgo-config During the initialization of the default "pgo-config" ConfigMap, there exists a case (likely a race condition that I did not track down) that triggered an error, but we were not catching the error. Regardless, we should, given the next line could trigger a panic. Immediate remediation without the patch should be just restarting the Operator Pod. Issue: [ch9826] Issue: #2075 --- internal/config/pgoconfig.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/config/pgoconfig.go b/internal/config/pgoconfig.go index ddb04cbf00..3fffa15a02 100644 --- a/internal/config/pgoconfig.go +++ b/internal/config/pgoconfig.go @@ -516,6 +516,11 @@ func (c *PgoConfig) GetConfig(clientset kubernetes.Interface, namespace string) cMap, err := initialize(clientset, namespace) + if err != nil { + log.Errorf("could not get ConfigMap: %s", err.Error()) + return err + } + //get the pgo.yaml config file str := cMap.Data[CONFIG_PATH] if str == "" { From 29cf5ae83aecf2c328993da03953fd8fd5a3cb2f Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 30 Nov 2020 16:27:01 -0500 Subject: [PATCH 022/276] Allow for pg_stat_statements collections from pgMonitor pgMonitor 4.4 introduced the ability to scrape metrics around pg_stat_statments, which include: - `ccp_pg_stat_statements_total_calls_count` Total number of queries run per user/database - `ccp_pg_stat_statements_total_exec_time_ms` Total runtime of all queries per user/database - `ccp_pg_stat_statements_total_mean_exec_time_ms` Mean runtime of all queries per user/database - ccp_pg_stat_statements_total_row_count Total rows returned from all queries per user/database While there may not be corresponding visuals in the pgMonitor Kubernetes overlay at this point, this at least allows for the collection and aggregation of these metrics. This also retitles an error message. Issue: [ch9841] Issue: #2036 --- bin/crunchy-postgres-exporter/start.sh | 38 +++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/bin/crunchy-postgres-exporter/start.sh b/bin/crunchy-postgres-exporter/start.sh index f8e02e4094..2a0d543b70 100755 --- a/bin/crunchy-postgres-exporter/start.sh +++ b/bin/crunchy-postgres-exporter/start.sh @@ -144,6 +144,12 @@ else else echo_err "Custom Query file queries_pg95.yml does not exist (it should).." fi + if [[ -f ${CONFIG_DIR?}/queries_pg_stat_statements_pg95.yml ]] + then + cat ${CONFIG_DIR?}/queries_pg_stat_statements_pg95.yml >> /tmp/queries.yml + else + echo_warn "Custom Query file queries_pg_stat_statements_pg95.yml not loaded." + fi elif (( ${VERSION?} >= 90600 )) && (( ${VERSION?} < 100000 )) then if [[ -f ${CONFIG_DIR?}/queries_pg96.yml ]] @@ -152,6 +158,12 @@ else else echo_err "Custom Query file queries_pg96.yml does not exist (it should).." fi + if [[ -f ${CONFIG_DIR?}/queries_pg_stat_statements_pg96.yml ]] + then + cat ${CONFIG_DIR?}/queries_pg_stat_statements_pg96.yml >> /tmp/queries.yml + else + echo_warn "Custom Query file queries_pg_stat_statements_pg96.yml not loaded." + fi elif (( ${VERSION?} >= 100000 )) && (( ${VERSION?} < 110000 )) then if [[ -f ${CONFIG_DIR?}/queries_pg10.yml ]] @@ -160,6 +172,12 @@ else else echo_err "Custom Query file queries_pg10.yml does not exist (it should).." fi + if [[ -f ${CONFIG_DIR?}/queries_pg_stat_statements_pg10.yml ]] + then + cat ${CONFIG_DIR?}/queries_pg_stat_statements_pg10.yml >> /tmp/queries.yml + else + echo_warn "Custom Query file queries_pg_stat_statements_pg10.yml not loaded." + fi elif (( ${VERSION?} >= 110000 )) && (( ${VERSION?} < 120000 )) then if [[ -f ${CONFIG_DIR?}/queries_pg11.yml ]] @@ -168,6 +186,12 @@ else else echo_err "Custom Query file queries_pg11.yml does not exist (it should).." fi + if [[ -f ${CONFIG_DIR?}/queries_pg_stat_statements_pg11.yml ]] + then + cat ${CONFIG_DIR?}/queries_pg_stat_statements_pg11.yml >> /tmp/queries.yml + else + echo_warn "Custom Query file queries_pg_stat_statements_pg11.yml not loaded." + fi elif (( ${VERSION?} >= 120000 )) && (( ${VERSION?} < 130000 )) then if [[ -f ${CONFIG_DIR?}/queries_pg12.yml ]] @@ -176,13 +200,25 @@ else else echo_err "Custom Query file queries_pg12.yml does not exist (it should).." fi + if [[ -f ${CONFIG_DIR?}/queries_pg_stat_statements_pg12.yml ]] + then + cat ${CONFIG_DIR?}/queries_pg_stat_statements_pg12.yml >> /tmp/queries.yml + else + echo_warn "Custom Query file queries_pg_stat_statements_pg12.yml not loaded." + fi elif (( ${VERSION?} >= 130000 )) then if [[ -f ${CONFIG_DIR?}/queries_pg13.yml ]] then cat ${CONFIG_DIR?}/queries_pg13.yml >> /tmp/queries.yml else - echo_err "Custom Query file queries_pg12.yml does not exist (it should).." + echo_err "Custom Query file queries_pg13.yml does not exist (it should).." + fi + if [[ -f ${CONFIG_DIR?}/queries_pg_stat_statements_pg13.yml ]] + then + cat ${CONFIG_DIR?}/queries_pg_stat_statements_pg13.yml >> /tmp/queries.yml + else + echo_warn "Custom Query file queries_pg_stat_statements_pg13.yml not loaded." fi else echo_err "Unknown or unsupported version of PostgreSQL. Exiting.." From a33f61be1375e5c93e1f2e666e3f6a3a7abbe9fc Mon Sep 17 00:00:00 2001 From: jmckulk Date: Fri, 20 Nov 2020 14:12:28 -0500 Subject: [PATCH 023/276] Compaction of pgo-sqlrunner into crunchy-postgres As part of the compaction effort the pgo-sqlrunner is now a running mode in the crunchy-postgres image. The pgo-sqlrunner image has been remove and related files have been moved to the Crunchy Containers repo. References to the image have been updated to use the crunchy-postgres image and the running mode `MODE: sqlrunner`. --- Makefile | 1 - bin/pull-from-gcr.sh | 1 - bin/push-to-gcr.sh | 1 - build/pgo-sqlrunner/Dockerfile | 45 ------------------- cmd/pgo-scheduler/scheduler/policy.go | 6 +-- cmd/pgo-scheduler/scheduler/types.go | 4 +- .../pgo-configs/pgo.sqlrunner-template.json | 12 +++-- .../olm/postgresoperator.csv.images.yaml | 1 - .../apiserver/scheduleservice/scheduleimpl.go | 4 +- internal/config/images.go | 2 - 10 files changed, 16 insertions(+), 61 deletions(-) delete mode 100644 build/pgo-sqlrunner/Dockerfile diff --git a/Makefile b/Makefile index 19e150820a..4f9803c436 100644 --- a/Makefile +++ b/Makefile @@ -84,7 +84,6 @@ images = pgo-apiserver \ pgo-event \ pgo-rmdata \ pgo-scheduler \ - pgo-sqlrunner \ pgo-client \ pgo-deployer \ crunchy-postgres-exporter \ diff --git a/bin/pull-from-gcr.sh b/bin/pull-from-gcr.sh index 25e4b267eb..3908630f43 100755 --- a/bin/pull-from-gcr.sh +++ b/bin/pull-from-gcr.sh @@ -21,7 +21,6 @@ IMAGES=( pgo-event pgo-backrest-repo pgo-scheduler - pgo-sqlrunner postgres-operator pgo-apiserver pgo-rmdata diff --git a/bin/push-to-gcr.sh b/bin/push-to-gcr.sh index 3ef6a11199..78832c3bda 100755 --- a/bin/push-to-gcr.sh +++ b/bin/push-to-gcr.sh @@ -19,7 +19,6 @@ IMAGES=( pgo-event pgo-backrest-repo pgo-scheduler -pgo-sqlrunner postgres-operator pgo-apiserver pgo-rmdata diff --git a/build/pgo-sqlrunner/Dockerfile b/build/pgo-sqlrunner/Dockerfile deleted file mode 100644 index 5b5dd2c45f..0000000000 --- a/build/pgo-sqlrunner/Dockerfile +++ /dev/null @@ -1,45 +0,0 @@ -ARG BASEOS -ARG BASEVER -ARG PREFIX -FROM ${PREFIX}/pgo-base:${BASEOS}-${BASEVER} - -ARG PGVERSION -ARG BACKREST_VERSION -ARG PACKAGER -ARG DFSET - -LABEL name="pgo-sqlrunner" \ - summary="Crunchy PostgreSQL Operator - SQL Runner" \ - description="Crunchy PostgreSQL Operator - SQL Runner" - -ENV PGROOT="/usr/pgsql-${PGVERSION}" - -RUN if [ "$DFSET" = "centos" ] ; then \ - ${PACKAGER} -y install epel-release \ - && ${PACKAGER} -y install \ - --setopt=skip_missing_names_on_install=False \ - gettext \ - hostname \ - nss_wrapper \ - procps-ng \ - postgresql${PGVERSION} \ - && ${PACKAGER} -y clean all ; \ -fi - -RUN if [ "$DFSET" = "rhel" ] ; then \ - ${PACKAGER} -y install \ - --setopt=skip_missing_names_on_install=False \ - postgresql${PGVERSION} \ - && ${PACKAGER} -y clean all ; \ -fi - -RUN mkdir -p /opt/cpm/bin /opt/cpm/conf /pgconf \ - && chown -R 26:26 /opt/cpm /pgconf - -ADD bin/pgo-sqlrunner /opt/cpm/bin - -VOLUME ["/pgconf"] - -USER 26 - -CMD ["/opt/cpm/bin/start.sh"] diff --git a/cmd/pgo-scheduler/scheduler/policy.go b/cmd/pgo-scheduler/scheduler/policy.go index bb81969951..e2be356d07 100644 --- a/cmd/pgo-scheduler/scheduler/policy.go +++ b/cmd/pgo-scheduler/scheduler/policy.go @@ -134,8 +134,8 @@ func (p PolicyJob) Run() { policyJob := PolicyTemplate{ JobName: name, ClusterName: p.cluster, - PGOImagePrefix: util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, p.ccpImagePrefix), - PGOImageTag: p.ccpImageTag, + CCPImagePrefix: util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, p.ccpImagePrefix), + CCPImageTag: p.ccpImageTag, PGHost: p.cluster, PGPort: cluster.Spec.Port, PGDatabase: p.database, @@ -177,7 +177,7 @@ func (p PolicyJob) Run() { } // set the container image to an override value, if one exists - operator.SetContainerImageOverride(config.CONTAINER_IMAGE_PGO_SQL_RUNNER, + operator.SetContainerImageOverride(config.CONTAINER_IMAGE_CRUNCHY_POSTGRES_HA, &newJob.Spec.Template.Spec.Containers[0]) _, err = clientset.BatchV1().Jobs(p.namespace).Create(ctx, newJob, metav1.CreateOptions{}) diff --git a/cmd/pgo-scheduler/scheduler/types.go b/cmd/pgo-scheduler/scheduler/types.go index 3838e4d994..674ef86ad2 100644 --- a/cmd/pgo-scheduler/scheduler/types.go +++ b/cmd/pgo-scheduler/scheduler/types.go @@ -63,8 +63,8 @@ type Policy struct { type PolicyTemplate struct { JobName string ClusterName string - PGOImagePrefix string - PGOImageTag string + CCPImagePrefix string + CCPImageTag string PGHost string PGPort string PGDatabase string diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json index 56dbf8b035..a301df048f 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json @@ -5,7 +5,7 @@ "name": "{{.JobName}}", "labels": { "vendor": "crunchydata", - "pgo-sqlrunner": "true", + "sqlrunner": "true", "pg-cluster": "{{.ClusterName}}" } }, @@ -15,7 +15,7 @@ "name": "{{.JobName}}", "labels": { "vendor": "crunchydata", - "pgo-sqlrunner": "true", + "sqlrunner": "true", "pg-cluster": "{{.ClusterName}}" } }, @@ -24,8 +24,14 @@ "containers": [ { "name": "sqlrunner", - "image": "{{.PGOImagePrefix}}/pgo-sqlrunner:{{.PGOImageTag}}", + "image": "{{.CCPImagePrefix}}/crunchy-postgres-ha:{{.CCPImageTag}}", + "command": ["/opt/crunchy/bin/uid_postgres.sh"], + "args": ["/opt/crunchy/bin/start.sh"], "env": [ + { + "name": "MODE", + "value": "sqlrunner" + }, { "name": "PG_HOST", "value": "{{.PGHost}}" diff --git a/installers/olm/postgresoperator.csv.images.yaml b/installers/olm/postgresoperator.csv.images.yaml index 301d117a67..87e04f05b0 100644 --- a/installers/olm/postgresoperator.csv.images.yaml +++ b/installers/olm/postgresoperator.csv.images.yaml @@ -8,7 +8,6 @@ - { name: RELATED_IMAGE_PGO_BACKREST_REPO, value: '${PGO_IMAGE_PREFIX}/pgo-backrest-repo:${PGO_IMAGE_TAG}' } - { name: RELATED_IMAGE_PGO_CLIENT, value: '${PGO_IMAGE_PREFIX}/pgo-client:${PGO_IMAGE_TAG}' } - { name: RELATED_IMAGE_PGO_RMDATA, value: '${PGO_IMAGE_PREFIX}/pgo-rmdata:${PGO_IMAGE_TAG}' } -- { name: RELATED_IMAGE_PGO_SQL_RUNNER, value: '${PGO_IMAGE_PREFIX}/pgo-sqlrunner:${PGO_IMAGE_TAG}' } - { name: RELATED_IMAGE_CRUNCHY_POSTGRES_EXPORTER, value: '${PGO_IMAGE_PREFIX}/crunchy-postgres-exporter:${PGO_IMAGE_TAG}' } - { name: RELATED_IMAGE_CRUNCHY_ADMIN, value: '${CCP_IMAGE_PREFIX}/crunchy-admin:${CCP_IMAGE_TAG}' } diff --git a/internal/apiserver/scheduleservice/scheduleimpl.go b/internal/apiserver/scheduleservice/scheduleimpl.go index 09830a03d2..7aa2a9e194 100644 --- a/internal/apiserver/scheduleservice/scheduleimpl.go +++ b/internal/apiserver/scheduleservice/scheduleimpl.go @@ -93,8 +93,8 @@ func (s scheduleRequest) createPolicySchedule(cluster *crv1.Pgcluster, ns string Name: s.Request.PolicyName, Database: s.Request.Database, Secret: s.Request.Secret, - ImagePrefix: util.GetValueOrDefault(cluster.Spec.PGOImagePrefix, apiserver.Pgo.Pgo.PGOImagePrefix), - ImageTag: apiserver.Pgo.Pgo.PGOImageTag, + ImagePrefix: util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, apiserver.Pgo.Cluster.CCPImagePrefix), + ImageTag: apiserver.Pgo.Cluster.CCPImageTag, }, } return schedule diff --git a/internal/config/images.go b/internal/config/images.go index 34845ee6a1..10a5227e13 100644 --- a/internal/config/images.go +++ b/internal/config/images.go @@ -21,7 +21,6 @@ const ( CONTAINER_IMAGE_PGO_BACKREST_REPO = "pgo-backrest-repo" CONTAINER_IMAGE_PGO_CLIENT = "pgo-client" CONTAINER_IMAGE_PGO_RMDATA = "pgo-rmdata" - CONTAINER_IMAGE_PGO_SQL_RUNNER = "pgo-sqlrunner" CONTAINER_IMAGE_CRUNCHY_ADMIN = "crunchy-admin" CONTAINER_IMAGE_CRUNCHY_BACKREST_RESTORE = "crunchy-backrest-restore" CONTAINER_IMAGE_CRUNCHY_POSTGRES_EXPORTER = "crunchy-postgres-exporter" @@ -46,7 +45,6 @@ var RelatedImageMap = map[string]string{ "RELATED_IMAGE_PGO_BACKREST_REPO": CONTAINER_IMAGE_PGO_BACKREST_REPO, "RELATED_IMAGE_PGO_CLIENT": CONTAINER_IMAGE_PGO_CLIENT, "RELATED_IMAGE_PGO_RMDATA": CONTAINER_IMAGE_PGO_RMDATA, - "RELATED_IMAGE_PGO_SQL_RUNNER": CONTAINER_IMAGE_PGO_SQL_RUNNER, "RELATED_IMAGE_CRUNCHY_ADMIN": CONTAINER_IMAGE_CRUNCHY_ADMIN, "RELATED_IMAGE_CRUNCHY_BACKREST_RESTORE": CONTAINER_IMAGE_CRUNCHY_BACKREST_RESTORE, "RELATED_IMAGE_CRUNCHY_POSTGRES_EXPORTER": CONTAINER_IMAGE_CRUNCHY_POSTGRES_EXPORTER, From 98a6d8f130f77e032a9f7fe51b77af52d7d4490a Mon Sep 17 00:00:00 2001 From: jmckulk Date: Fri, 20 Nov 2020 14:16:32 -0500 Subject: [PATCH 024/276] Updates to use compacted crunchy-postgres image This change updates code and templates that reference any old image that is now part of the compacted crunchy-postgres image. Templates have been updated to use crunchy-postgres as the image and pass in the relevant running mode env variable. The code has been updated to use the new `/opt/crunchy` path instead of `/opt/cpm`. The crunchy-pgdump and crunchy-pgrestore images were compacted into the crunchy-postgres image. This change removes references to the old images from pull scripts and images.go. The update to images.go removes the related images for each of the compacted images and updates the code to use the related image for crunchy-postgres. --- bin/pull-ccp-from-gcr.sh | 2 -- .../postgres-operator-containers-overview.md | 13 ++++++------- .../files/pgo-configs/cluster-deployment.json | 8 ++++++-- .../pgo-operator/files/pgo-configs/pgdump-job.json | 8 +++++++- .../files/pgo-configs/pgrestore-job.json | 8 +++++++- installers/olm/postgresoperator.csv.images.yaml | 2 -- internal/config/images.go | 4 ---- internal/operator/cluster/pgbouncer.go | 4 ++-- internal/operator/cluster/standby.go | 2 +- internal/operator/config/localdb.go | 6 +++--- internal/operator/pgdump/dump.go | 2 +- internal/operator/pgdump/restore.go | 2 +- 12 files changed, 34 insertions(+), 27 deletions(-) diff --git a/bin/pull-ccp-from-gcr.sh b/bin/pull-ccp-from-gcr.sh index 0e6dc20aea..17ce4ae360 100755 --- a/bin/pull-ccp-from-gcr.sh +++ b/bin/pull-ccp-from-gcr.sh @@ -8,8 +8,6 @@ IMAGES=( crunchy-postgres-ha crunchy-pgbadger crunchy-pgbouncer - crunchy-pgdump - crunchy-pgrestore ) function echo_green() { diff --git a/docs/content/architecture/postgres-operator-containers-overview.md b/docs/content/architecture/postgres-operator-containers-overview.md index 028b3b1f74..4397c63841 100644 --- a/docs/content/architecture/postgres-operator-containers-overview.md +++ b/docs/content/architecture/postgres-operator-containers-overview.md @@ -9,9 +9,13 @@ weight: 600 The PostgreSQL Operator orchestrates a series of PostgreSQL and PostgreSQL related containers containers that enable rapid deployment of PostgreSQL, including administration and monitoring tools in a Kubernetes environment. The PostgreSQL Operator supports PostgreSQL 9.5+ with multiple PostgreSQL cluster deployment strategies and a variety of PostgreSQL related extensions and tools enabling enterprise grade PostgreSQL-as-a-Service. A full list of the containers supported by the PostgreSQL Operator is provided below. -### PostgreSQL Server and Extensions +### PostgreSQL Server, Tools, and Extensions -* **PostgreSQL** (crunchy-postgres-ha). PostgreSQL database server. The crunchy-postgres container image is unmodified, open source PostgreSQL packaged and maintained by Crunchy Data. +* **PostgreSQL** (crunchy-postgres-ha). PostgreSQL database server. The crunchy-postgres container image is unmodified, open source PostgreSQL packaged and maintained by Crunchy Data. The container supports PostgreSQL tools by running in different modes, more information on running modes can be found in the [Crunchy Container](https://access.crunchydata.com/documentation/crunchy-postgres-containers/latest/) documentation. The PostgreSQL operator uses the following running modes: + + - **pgdump** (MODE: pgdump) running in pgdump mode, the image executes either a pg_dump or pg_dumpall database backup against another PostgreSQL database. + - **pgrestore** (MODE: pgrestore) running in pgrestore mode, the image provides a means of performing a restore of a dump from pg_dump or pg_dumpall via psql or pg_restore to a PostgreSQL container database. + - **sqlrunner** (MODE: sqlrunner) running in sqlrunner mode, the image will use `psql` to issue specified queries, defined in SQL files, to a PostgreSQL container database. * **PostGIS** (crunchy-postgres-ha-gis). PostgreSQL database server including the PostGIS extension. The crunchy-postgres-gis container image is unmodified, open source PostgreSQL packaged and maintained by Crunchy Data. This image is identical to the crunchy-postgres image except it includes the open source geospatial extension PostGIS for PostgreSQL in addition to the language extension PL/R which allows for writing functions in the R statistical computing language. @@ -19,11 +23,6 @@ The PostgreSQL Operator orchestrates a series of PostgreSQL and PostgreSQL relat * **pgBackRest** (crunchy-postgres-ha). pgBackRest is a high performance backup and restore utility for PostgreSQL. The crunchy-postgres-ha container executes the pgBackRest utility, allowing FULL and DELTA restore capability. -* **pgdump** (crunchy-pgdump). The crunchy-pgdump container executes either a pg_dump or pg_dumpall database backup against another PostgreSQL database. - -* **crunchy-pgrestore** (restore). The restore image provides a means of performing a restore of a dump from pg_dump or pg_dumpall via psql or pg_restore to a PostgreSQL container database. - - ### Administration Tools * **pgAdmin4** (crunchy-pgadmin4). PGAdmin4 is a graphical user interface administration tool for PostgreSQL. The crunchy-pgadmin4 container executes the pgAdmin4 web application. diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json index 4a44785b27..7fd77e6449 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json @@ -39,7 +39,7 @@ "readinessProbe": { "exec": { "command": [ - "/opt/cpm/bin/health/pgha-readiness.sh" + "/opt/crunchy/bin/postgres-ha/health/pgha-readiness.sh" ] }, "initialDelaySeconds": 15 @@ -47,7 +47,7 @@ "livenessProbe": { "exec": { "command": [ - "/opt/cpm/bin/health/pgha-liveness.sh" + "/opt/crunchy/bin/postgres-ha/health/pgha-liveness.sh" ] }, "initialDelaySeconds": 30, @@ -56,6 +56,10 @@ }, {{.ContainerResources }} "env": [{ + "name": "MODE", + "value": "postgres" + }, + { "name": "PGHA_PG_PORT", "value": "{{.Port}}" }, { diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgdump-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgdump-job.json index 3b827ecaac..ef6e1b6d5a 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgdump-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgdump-job.json @@ -33,7 +33,9 @@ "serviceAccountName": "pgo-default", "containers": [{ "name": "pgdump", - "image": "{{.CCPImagePrefix}}/crunchy-pgdump:{{.CCPImageTag}}", + "image": "{{.CCPImagePrefix}}/crunchy-postgres-ha:{{.CCPImageTag}}", + "command": ["/opt/crunchy/bin/uid_postgres.sh"], + "args": ["/opt/crunchy/bin/start.sh"], "volumeMounts": [ { "mountPath": "/pgdata", @@ -42,6 +44,10 @@ } ], "env": [ + { + "name": "MODE", + "value": "pgdump" + }, { "name": "PGDUMP_HOST", "value": "{{.PgDumpHost}}" diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json index 4dae8fda14..3759905e95 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json @@ -34,7 +34,9 @@ "containers": [ { "name": "pgrestore", - "image": "{{.CCPImagePrefix}}/crunchy-pgrestore:{{.CCPImageTag}}", + "image": "{{.CCPImagePrefix}}/crunchy-postgres-ha:{{.CCPImageTag}}", + "command": ["/opt/crunchy/bin/uid_postgres.sh"], + "args": ["/opt/crunchy/bin/start.sh"], "volumeMounts": [ { "mountPath": "/pgdata", @@ -43,6 +45,10 @@ } ], "env": [ + { + "name": "MODE", + "value": "pgrestore" + }, { "name": "PGRESTORE_USER", "valueFrom": { diff --git a/installers/olm/postgresoperator.csv.images.yaml b/installers/olm/postgresoperator.csv.images.yaml index 87e04f05b0..429a882893 100644 --- a/installers/olm/postgresoperator.csv.images.yaml +++ b/installers/olm/postgresoperator.csv.images.yaml @@ -15,7 +15,5 @@ - { name: RELATED_IMAGE_CRUNCHY_PGADMIN, value: '${CCP_IMAGE_PREFIX}/crunchy-pgadmin4:${CCP_IMAGE_TAG}' } - { name: RELATED_IMAGE_CRUNCHY_PGBADGER, value: '${CCP_IMAGE_PREFIX}/crunchy-pgbadger:${CCP_IMAGE_TAG}' } - { name: RELATED_IMAGE_CRUNCHY_PGBOUNCER, value: '${CCP_IMAGE_PREFIX}/crunchy-pgbouncer:${CCP_IMAGE_TAG}' } -- { name: RELATED_IMAGE_CRUNCHY_PGDUMP, value: '${CCP_IMAGE_PREFIX}/crunchy-pgdump:${CCP_IMAGE_TAG}' } -- { name: RELATED_IMAGE_CRUNCHY_PGRESTORE, value: '${CCP_IMAGE_PREFIX}/crunchy-pgrestore:${CCP_IMAGE_TAG}' } - { name: RELATED_IMAGE_CRUNCHY_POSTGRES_HA, value: '${CCP_IMAGE_PREFIX}/crunchy-postgres-ha:${CCP_IMAGE_TAG}' } - { name: RELATED_IMAGE_CRUNCHY_POSTGRES_GIS_HA, value: '${CCP_IMAGE_PREFIX}/crunchy-postgres-gis-ha:${CCP_POSTGIS_IMAGE_TAG}' } diff --git a/internal/config/images.go b/internal/config/images.go index 10a5227e13..71c0af7c1c 100644 --- a/internal/config/images.go +++ b/internal/config/images.go @@ -28,8 +28,6 @@ const ( CONTAINER_IMAGE_CRUNCHY_PGADMIN = "crunchy-pgadmin4" CONTAINER_IMAGE_CRUNCHY_PGBADGER = "crunchy-pgbadger" CONTAINER_IMAGE_CRUNCHY_PGBOUNCER = "crunchy-pgbouncer" - CONTAINER_IMAGE_CRUNCHY_PGDUMP = "crunchy-pgdump" - CONTAINER_IMAGE_CRUNCHY_PGRESTORE = "crunchy-pgrestore" CONTAINER_IMAGE_CRUNCHY_POSTGRES_HA = "crunchy-postgres-ha" CONTAINER_IMAGE_CRUNCHY_POSTGRES_GIS_HA = "crunchy-postgres-gis-ha" CONTAINER_IMAGE_CRUNCHY_PROMETHEUS = "crunchy-prometheus" @@ -51,8 +49,6 @@ var RelatedImageMap = map[string]string{ "RELATED_IMAGE_CRUNCHY_PGADMIN": CONTAINER_IMAGE_CRUNCHY_PGADMIN, "RELATED_IMAGE_CRUNCHY_PGBADGER": CONTAINER_IMAGE_CRUNCHY_PGBADGER, "RELATED_IMAGE_CRUNCHY_PGBOUNCER": CONTAINER_IMAGE_CRUNCHY_PGBOUNCER, - "RELATED_IMAGE_CRUNCHY_PGDUMP": CONTAINER_IMAGE_CRUNCHY_PGDUMP, - "RELATED_IMAGE_CRUNCHY_PGRESTORE": CONTAINER_IMAGE_CRUNCHY_PGRESTORE, "RELATED_IMAGE_CRUNCHY_POSTGRES_HA": CONTAINER_IMAGE_CRUNCHY_POSTGRES_HA, "RELATED_IMAGE_CRUNCHY_POSTGRES_GIS_HA": CONTAINER_IMAGE_CRUNCHY_POSTGRES_GIS_HA, } diff --git a/internal/operator/cluster/pgbouncer.go b/internal/operator/cluster/pgbouncer.go index 9c70884faa..cf8dcaf7c0 100644 --- a/internal/operator/cluster/pgbouncer.go +++ b/internal/operator/cluster/pgbouncer.go @@ -88,10 +88,10 @@ const pgPort = "5432" const ( // the path to the pgbouncer uninstallation script script - pgBouncerUninstallScript = "/opt/cpm/bin/sql/pgbouncer/pgbouncer-uninstall.sql" + pgBouncerUninstallScript = "/opt/crunchy/bin/postgres-ha/sql/pgbouncer/pgbouncer-uninstall.sql" // the path to the pgbouncer installation script - pgBouncerInstallScript = "/opt/cpm/bin/sql/pgbouncer/pgbouncer-install.sql" + pgBouncerInstallScript = "/opt/crunchy/bin/postgres-ha/sql/pgbouncer/pgbouncer-install.sql" ) const ( diff --git a/internal/operator/cluster/standby.go b/internal/operator/cluster/standby.go index 1444e78a45..30bcc7edbe 100644 --- a/internal/operator/cluster/standby.go +++ b/internal/operator/cluster/standby.go @@ -58,7 +58,7 @@ const ( "create_replica_methods": [ "pgbackrest_standby" ], - "restore_command": "source /opt/cpm/bin/pgbackrest/pgbackrest-set-env.sh && pgbackrest archive-get %f \"%p\"" + "restore_command": "source /opt/crunchy/bin/postgres-ha/pgbackrest/pgbackrest-set-env.sh && pgbackrest archive-get %f \"%p\"" }` ) diff --git a/internal/operator/config/localdb.go b/internal/operator/config/localdb.go index 797c53544f..d7eef19bf8 100644 --- a/internal/operator/config/localdb.go +++ b/internal/operator/config/localdb.go @@ -39,13 +39,13 @@ var ( // readConfigCMD is the command used to read local cluster configuration in a database // container readConfigCMD []string = []string{"bash", "-c", - "/opt/cpm/bin/yq r /tmp/postgres-ha-bootstrap.yaml postgresql | " + - "/opt/cpm/bin/yq p - postgresql", + "/opt/crunchy/bin/yq r /tmp/postgres-ha-bootstrap.yaml postgresql | " + + "/opt/crunchy/bin/yq p - postgresql", } // applyAndReloadConfigCMD is the command for calling the script to apply and reload the local // configuration for a database container. The required arguments are appended to this command // when the script is called. - applyAndReloadConfigCMD []string = []string{"/opt/cpm/bin/common/pgha-reload-local.sh"} + applyAndReloadConfigCMD []string = []string{"/opt/crunchy/bin/postgres-ha/common/pgha-reload-local.sh"} // pghaLocalConfigName represents the name of the local configuration stored for each database // server in the "-pgha-config" configMap, which is "-local-config" diff --git a/internal/operator/pgdump/dump.go b/internal/operator/pgdump/dump.go index 940713b3a0..060043d8c1 100644 --- a/internal/operator/pgdump/dump.go +++ b/internal/operator/pgdump/dump.go @@ -139,7 +139,7 @@ func Dump(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask) { } // set the container image to an override value, if one exists - operator.SetContainerImageOverride(config.CONTAINER_IMAGE_CRUNCHY_PGDUMP, + operator.SetContainerImageOverride(config.CONTAINER_IMAGE_CRUNCHY_POSTGRES_HA, &newjob.Spec.Template.Spec.Containers[0]) _, err = clientset.BatchV1().Jobs(namespace).Create(ctx, &newjob, metav1.CreateOptions{}) diff --git a/internal/operator/pgdump/restore.go b/internal/operator/pgdump/restore.go index 57cc0f7b12..6d874f4a9e 100644 --- a/internal/operator/pgdump/restore.go +++ b/internal/operator/pgdump/restore.go @@ -115,7 +115,7 @@ func Restore(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask) { } // set the container image to an override value, if one exists - operator.SetContainerImageOverride(config.CONTAINER_IMAGE_CRUNCHY_PGRESTORE, + operator.SetContainerImageOverride(config.CONTAINER_IMAGE_CRUNCHY_POSTGRES_HA, &newjob.Spec.Template.Spec.Containers[0]) j, err := clientset.BatchV1().Jobs(namespace).Create(ctx, &newjob, metav1.CreateOptions{}) From 4de7d3f154ac65b9e7898b034ddae903bd027697 Mon Sep 17 00:00:00 2001 From: andrewlecuyer <43458182+andrewlecuyer@users.noreply.github.com> Date: Wed, 2 Dec 2020 10:16:22 -0600 Subject: [PATCH 025/276] Remove disabling of autofailover from rmdata The rmdata application no longer disables autofailover when deleting a PostgreSQL cluster. In past versions of the PostgreSQL Operator, it was necessary to first disable autofailover prior to cluster deletion since the rmdata application itself would stop the PostgreSQL database using 'pg_ctl stop'. However, since Patroni is now responsible for cleanly shutting down the database (specifically upon receipt of a SIGTERM signal), autofailver should no longer be disabled (if it is, Patroni will not respond to the SIGTERM and will therefore not attempt to cleanly shutdown the database). This commit therefore ensures an attempt is made to cleanly shutdown the database when deleting a PostgreSQL cluster. This, in turn, will increase the likeliness that the cluster can later be recreated and cleanly restarted. Issue: [ch9856] --- cmd/pgo-rmdata/process.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/cmd/pgo-rmdata/process.go b/cmd/pgo-rmdata/process.go index 85a7e4ce4b..d0d79744f6 100644 --- a/cmd/pgo-rmdata/process.go +++ b/cmd/pgo-rmdata/process.go @@ -49,15 +49,6 @@ func Delete(request Request) { ctx := context.TODO() log.Infof("rmdata.Process %v", request) - // if, check to see if this is a full cluster removal...i.e. "IsReplica" - // and "IsBackup" is set to false - // - // if this is a full cluster removal, first disable autofailover - if !(request.IsReplica || request.IsBackup) { - log.Debug("disabling autofailover for cluster removal") - util.ToggleAutoFailover(request.Clientset, false, request.ClusterPGHAScope, request.Namespace) - } - //the case of 'pgo scaledown' if request.IsReplica { log.Info("rmdata.Process scaledown replica use case") From b1e5421768f5b4c9fe519384af2b9aefd069d6cc Mon Sep 17 00:00:00 2001 From: Joseph Mckulka <16840147+jmckulk@users.noreply.github.com> Date: Wed, 2 Dec 2020 12:31:57 -0500 Subject: [PATCH 026/276] Remove orphaned error check This error check is using an `err` variable that is defined and handled earlier in the code, which leads to an extra error in the logs. --- internal/apiserver/clusterservice/clusterimpl.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index e099d0303f..90a062e40d 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -722,9 +722,6 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. //set the metrics flag with the global setting first userLabelsMap[config.LABEL_EXPORTER] = strconv.FormatBool(apiserver.MetricsFlag) - if err != nil { - log.Error(err) - } //if metrics is chosen on the pgo command, stick it into the user labels if request.MetricsFlag { From a8bd519aa3881c011fdfa965d08c6eace1f98ef1 Mon Sep 17 00:00:00 2001 From: andrewlecuyer <43458182+andrewlecuyer@users.noreply.github.com> Date: Mon, 7 Dec 2020 09:15:35 -0600 Subject: [PATCH 027/276] Delta Restore for In-Place Cluster Restore When performing an in-place PostgreSQL cluster restore (such as when using the 'pgo restore' command), the PVC for the current primary database (includes the PGDATA PVC, along with any WAL and/or tablespace PVCs) will now be preserved if found (as identified by the 'current- primary' annotation on the pgcluster custom resource). This will cause the 'crunchy-postgres-ha' container to attempt a pgBackRest "delta" restore when bootstrapping the restored cluster, therefore leveraging any existing data within the PGDATA directory efficiently restore the database. Issue: [ch9878] --- internal/operator/backrest/restore.go | 19 ++++++++++++++++--- internal/operator/cluster/clusterlogic.go | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/internal/operator/backrest/restore.go b/internal/operator/backrest/restore.go index 1b37817137..e6ce666f22 100644 --- a/internal/operator/backrest/restore.go +++ b/internal/operator/backrest/restore.go @@ -116,7 +116,6 @@ func PrepareClusterForRestore(clientset kubeapi.Interface, cluster *crv1.Pgclust patch, err := kubeapi.NewMergePatch(). Add("metadata", "annotations")(map[string]string{ config.ANNOTATION_BACKREST_RESTORE: "", - config.ANNOTATION_CURRENT_PRIMARY: clusterName, }). Add("metadata", "labels")(map[string]string{ config.LABEL_DEPLOYMENT_NAME: clusterName, @@ -200,7 +199,7 @@ func PrepareClusterForRestore(clientset kubeapi.Interface, cluster *crv1.Pgclust // find all database PVCs for the entire PostgreSQL cluster. Includes the PVCs for all PGDATA // volumes, as well as the PVCs for any WAL and/or tablespace volumes - databasePVCList, err := getPGDatabasePVCNames(clientset, replicas, clusterName, namespace) + databasePVCList, err := getPGDatabasePVCNames(clientset, replicas, cluster) if err != nil { return nil, err } @@ -316,9 +315,12 @@ func PublishRestore(id, clusterName, username, namespace string) { // instances comprising the cluster, in addition to any additional volumes used by those // instances, e.g. PVCs for external WAL and/or tablespace volumes. func getPGDatabasePVCNames(clientset kubeapi.Interface, replicas *crv1.PgreplicaList, - clusterName, namespace string) ([]string, error) { + cluster *crv1.Pgcluster) ([]string, error) { ctx := context.TODO() + namespace := cluster.Namespace + clusterName := cluster.Name + // create a slice with the names of all database instances in the cluster. Even though the // original primary database (with a name matching the cluster name) might no longer exist, // add the cluster name to this list in the event that it does, along with the names of any @@ -338,9 +340,20 @@ func getPGDatabasePVCNames(clientset kubeapi.Interface, replicas *crv1.Pgreplica } var databasePVCList []string + primary := cluster.Annotations[config.ANNOTATION_CURRENT_PRIMARY] + for _, instance := range instances { for _, clusterPVC := range clusterPVCList.Items { + pvcName := clusterPVC.GetName() + + // Keep the current primary PVC's in order to attempt a pgBackRest delta restore. + // Includes the PGDATA PVC, as well as any WAL and/or tablespace PVC's if present. + if pvcName == primary || pvcName == fmt.Sprintf(walPVCPattern, primary) || + strings.HasPrefix(pvcName, fmt.Sprintf(tablespacePVCSuffixPattern, primary)) { + continue + } + if pvcName == instance || pvcName == fmt.Sprintf(walPVCPattern, instance) || strings.HasPrefix(pvcName, fmt.Sprintf(tablespacePVCSuffixPattern, instance)) { databasePVCList = append(databasePVCList, pvcName) diff --git a/internal/operator/cluster/clusterlogic.go b/internal/operator/cluster/clusterlogic.go index 7b78d66f00..f6ddf85fca 100644 --- a/internal/operator/cluster/clusterlogic.go +++ b/internal/operator/cluster/clusterlogic.go @@ -235,7 +235,7 @@ func getBootstrapJobFields(clientset kubeapi.Interface, // Now override any backrest env vars for the bootstrap job bootstrapBackrestVars, err := operator.GetPgbackrestBootstrapEnvVars(restoreClusterName, - cluster.GetName(), restoreFromSecret) + cluster.GetAnnotations()[config.ANNOTATION_CURRENT_PRIMARY], restoreFromSecret) if err != nil { return bootstrapFields, err } From 102f345ff118d39d0bf0d5d723c9ed111f84be37 Mon Sep 17 00:00:00 2001 From: Joseph Mckulka <16840147+jmckulk@users.noreply.github.com> Date: Mon, 7 Dec 2020 10:17:11 -0500 Subject: [PATCH 028/276] pgo-backrest and pgo-backrest-repo containers cleanup The pgo-backrest and pgo-backrest-repo containers have been moved to the Crunchy Containers project. As such, the associated files are no longer needed in this repository. Additionally, the references to these containers are now updated to match the new naming convention being used, and the image tag and prefix values are updated to reflect the new location of the containers. This change removes debug flags and references to the unused sshd_port env variable. It also fixes a minor copy paste error in the docs. Co-authored-by: TJ Moore --- Makefile | 6 - bin/pgo-backrest-repo/archive-push-s3.sh | 3 - bin/pgo-backrest-repo/pgo-backrest-repo.sh | 81 --------- bin/pgo-backrest/.gitignore | 1 - bin/pgo-backrest/README.txt | 3 - bin/pgo-backrest/pgo-backrest.sh | 23 --- bin/pull-from-gcr.sh | 2 - bin/push-to-gcr.sh | 2 - bin/uid_pgbackrest.sh | 22 --- build/pgo-backrest-repo/Dockerfile | 46 ------ build/pgo-backrest/Dockerfile | 31 ---- cmd/pgo-backrest/main.go | 154 ------------------ cmd/pgo-scheduler/scheduler/pgbackrest.go | 4 +- .../files/pgo-configs/backrest-job.json | 5 +- .../pgo-backrest-repo-template.json | 10 +- .../olm/postgresoperator.csv.images.yaml | 4 +- .../apiserver/backrestservice/backrestimpl.go | 2 +- internal/config/annotations.go | 2 +- internal/config/images.go | 4 +- internal/operator/backrest/backup.go | 10 +- internal/operator/backrest/repo.go | 8 +- internal/operator/backrest/restore.go | 4 +- internal/operator/backrest/stanza.go | 4 +- 23 files changed, 30 insertions(+), 401 deletions(-) delete mode 100755 bin/pgo-backrest-repo/archive-push-s3.sh delete mode 100755 bin/pgo-backrest-repo/pgo-backrest-repo.sh delete mode 100644 bin/pgo-backrest/.gitignore delete mode 100644 bin/pgo-backrest/README.txt delete mode 100755 bin/pgo-backrest/pgo-backrest.sh delete mode 100755 bin/uid_pgbackrest.sh delete mode 100644 build/pgo-backrest-repo/Dockerfile delete mode 100644 build/pgo-backrest/Dockerfile delete mode 100644 cmd/pgo-backrest/main.go diff --git a/Makefile b/Makefile index 4f9803c436..1488c2dc7c 100644 --- a/Makefile +++ b/Makefile @@ -79,8 +79,6 @@ endif # To build a specific image, run 'make -image' (e.g. 'make pgo-apiserver-image') images = pgo-apiserver \ - pgo-backrest \ - pgo-backrest-repo \ pgo-event \ pgo-rmdata \ pgo-scheduler \ @@ -117,9 +115,6 @@ deployoperator: build-pgo-apiserver: $(GO_BUILD) -o bin/apiserver ./cmd/apiserver -build-pgo-backrest: - $(GO_BUILD) -o bin/pgo-backrest/pgo-backrest ./cmd/pgo-backrest - build-pgo-rmdata: $(GO_BUILD) -o bin/pgo-rmdata/pgo-rmdata ./cmd/pgo-rmdata @@ -216,7 +211,6 @@ clean: clean-deprecated rm -f bin/apiserver rm -f bin/postgres-operator rm -f bin/pgo bin/pgo-mac bin/pgo.exe - rm -f bin/pgo-backrest/pgo-backrest rm -f bin/pgo-rmdata/pgo-rmdata rm -f bin/pgo-scheduler/pgo-scheduler [ -z "$$(ls hack/tools)" ] || rm hack/tools/* diff --git a/bin/pgo-backrest-repo/archive-push-s3.sh b/bin/pgo-backrest-repo/archive-push-s3.sh deleted file mode 100755 index 2cafa76d90..0000000000 --- a/bin/pgo-backrest-repo/archive-push-s3.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -pgbackrest "$@" diff --git a/bin/pgo-backrest-repo/pgo-backrest-repo.sh b/bin/pgo-backrest-repo/pgo-backrest-repo.sh deleted file mode 100755 index 25fdec5f69..0000000000 --- a/bin/pgo-backrest-repo/pgo-backrest-repo.sh +++ /dev/null @@ -1,81 +0,0 @@ -#!/bin/bash - -# Copyright 2019 - 2020 Crunchy Data Solutions, Inc. -# 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. - - -function trap_sigterm() { - echo "Signal trap triggered, beginning shutdown.." - killall sshd -} - -trap 'trap_sigterm' SIGINT SIGTERM - -echo "Starting the pgBackRest repo" - -CONFIG=/sshd -REPO=/backrestrepo - -if [ ! -d $PGBACKREST_REPO1_PATH ]; then - echo "creating " $PGBACKREST_REPO1_PATH - mkdir -p $PGBACKREST_REPO1_PATH -fi - -# This is a workaround for changes introduced in pgBackRest v2.24. Specifically, a pg1-path -# setting must now be visible when another container executes a pgBackRest command via SSH. -# Since env vars, and therefore the PGBACKREST_DB_PATH setting, is not visible when another -# container executes a command via SSH, this adds the pg1-path setting to the pgBackRest config -# file instead, ensuring the setting is always available in the environment during SSH calls. -# Additionally, since the value for pg1-path setting in the repository container is irrelevant -# (i.e. the value specified by the container running the command via SSH is used instead), it is -# simply set to a dummy directory within the config file. -# If the URI style is set to 'path' instead of the default 'host' value, pgBackRest will -# connect to S3 by prependinging bucket names to URIs instead of the default 'bucket.endpoint' style -# Finally, if TLS verification is set to 'n', pgBackRest disables verification of the S3 server -# certificate. -mkdir -p /tmp/pg1path -if ! grep -Fxq "[${PGBACKREST_STANZA}]" "/etc/pgbackrest/pgbackrest.conf" 2> /dev/null -then - - printf "[%s]\npg1-path=/tmp/pg1path\n" "$PGBACKREST_STANZA" > /etc/pgbackrest/pgbackrest.conf - - # Additionally, if the PGBACKREST S3 variables are set, add them here - if [[ "${PGBACKREST_REPO1_S3_KEY}" != "" ]] - then - printf "repo1-s3-key=%s\n" "${PGBACKREST_REPO1_S3_KEY}" >> /etc/pgbackrest/pgbackrest.conf - fi - - if [[ "${PGBACKREST_REPO1_S3_KEY_SECRET}" != "" ]] - then - printf "repo1-s3-key-secret=%s\n" "${PGBACKREST_REPO1_S3_KEY_SECRET}" >> /etc/pgbackrest/pgbackrest.conf - fi - - if [[ "${PGBACKREST_REPO1_S3_URI_STYLE}" != "" ]] - then - printf "repo1-s3-uri-style=%s\n" "${PGBACKREST_REPO1_S3_URI_STYLE}" >> /etc/pgbackrest/pgbackrest.conf - fi - -fi - -mkdir -p ~/.ssh/ -cp $CONFIG/config ~/.ssh/ -#cp $CONFIG/authorized_keys ~/.ssh/ -cp $CONFIG/id_ed25519 /tmp -chmod 400 /tmp/id_ed25519 ~/.ssh/config - -# start sshd which is used by pgbackrest for remote connections -/usr/sbin/sshd -D -f $CONFIG/sshd_config & - -echo "The pgBackRest repo has been started" - -wait diff --git a/bin/pgo-backrest/.gitignore b/bin/pgo-backrest/.gitignore deleted file mode 100644 index 230c647366..0000000000 --- a/bin/pgo-backrest/.gitignore +++ /dev/null @@ -1 +0,0 @@ -pgo-backrest diff --git a/bin/pgo-backrest/README.txt b/bin/pgo-backrest/README.txt deleted file mode 100644 index 23f92ef4a4..0000000000 --- a/bin/pgo-backrest/README.txt +++ /dev/null @@ -1,3 +0,0 @@ -pgo-backrest binary goes in this directory and gets -copied into the pgo-backrest image, .gitignore is here -to keep the binary from making its way into github diff --git a/bin/pgo-backrest/pgo-backrest.sh b/bin/pgo-backrest/pgo-backrest.sh deleted file mode 100755 index fda20af57c..0000000000 --- a/bin/pgo-backrest/pgo-backrest.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh - -# Copyright 2018 - 2020 Crunchy Data Solutions, Inc. -# 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. - - -/opt/cpm/bin/pgo-backrest - -echo $UID "is the UID in the script" - -chown -R $UID:$UID $PGBACKREST_DB_PATH - -chmod -R o+rx $PGBACKREST_DB_PATH diff --git a/bin/pull-from-gcr.sh b/bin/pull-from-gcr.sh index 3908630f43..0e57fc13db 100755 --- a/bin/pull-from-gcr.sh +++ b/bin/pull-from-gcr.sh @@ -19,12 +19,10 @@ REGISTRY='us.gcr.io/container-suite' VERSION=$PGO_IMAGE_TAG IMAGES=( pgo-event - pgo-backrest-repo pgo-scheduler postgres-operator pgo-apiserver pgo-rmdata - pgo-backrest pgo-client pgo-deployer crunchy-postgres-exporter diff --git a/bin/push-to-gcr.sh b/bin/push-to-gcr.sh index 78832c3bda..4bc46b933c 100755 --- a/bin/push-to-gcr.sh +++ b/bin/push-to-gcr.sh @@ -17,12 +17,10 @@ GCR_IMAGE_PREFIX=gcr.io/crunchy-dev-test IMAGES=( pgo-event -pgo-backrest-repo pgo-scheduler postgres-operator pgo-apiserver pgo-rmdata -pgo-backrest pgo-client pgo-deployer crunchy-postgres-exporter diff --git a/bin/uid_pgbackrest.sh b/bin/uid_pgbackrest.sh deleted file mode 100755 index 3f9c9d1957..0000000000 --- a/bin/uid_pgbackrest.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -if ! whoami &> /dev/null -then - if [[ -w /etc/passwd ]] - then - sed "/pgbackrest:x:2000:/d" /etc/passwd >> /tmp/uid.tmp - cp /tmp/uid.tmp /etc/passwd - rm -f /tmp/uid.tmp - echo "${USER_NAME:-pgbackrest}:x:$(id -u):0:${USER_NAME:-pgbackrest} user:${HOME}:/bin/bash" >> /etc/passwd - fi - - if [[ -w /etc/group ]] - then - sed "/pgbackrest:x:2000/d" /etc/group >> /tmp/gid.tmp - cp /tmp/gid.tmp /etc/group - rm -f /tmp/gid.tmp - echo "nfsnobody:x:65534:" >> /etc/group - echo "pgbackrest:x:$(id -g):pgbackrest" >> /etc/group - fi -fi -exec "$@" diff --git a/build/pgo-backrest-repo/Dockerfile b/build/pgo-backrest-repo/Dockerfile deleted file mode 100644 index 0d0e1dd6b8..0000000000 --- a/build/pgo-backrest-repo/Dockerfile +++ /dev/null @@ -1,46 +0,0 @@ -ARG BASEOS -ARG BASEVER -ARG PREFIX -FROM ${PREFIX}/pgo-base:${BASEOS}-${BASEVER} - -ARG BACKREST_VERSION -ARG PACKAGER -ARG DFSET - -LABEL name="pgo-backrest-repo" \ - summary="Crunchy PostgreSQL Operator - pgBackRest Repository" \ - description="Crunchy PostgreSQL Operator - pgBackRest Repository" - -RUN ${PACKAGER} -y install \ - --setopt=skip_missing_names_on_install=False \ - crunchy-backrest-"${BACKREST_VERSION}" \ - hostname \ - openssh-clients \ - openssh-server \ - procps-ng \ - psmisc \ - rsync \ - && ${PACKAGER} -y clean all - -RUN groupadd pgbackrest -g 2000 && useradd pgbackrest -u 2000 -g 2000 -ADD bin/pgo-backrest-repo /usr/local/bin -RUN chmod +x /usr/local/bin/pgo-backrest-repo.sh /usr/local/bin/archive-push-s3.sh \ - && mkdir -p /opt/cpm/bin /etc/pgbackrest \ - && chown -R pgbackrest:pgbackrest /opt/cpm \ - && chown -R pgbackrest /etc/pgbackrest - -ADD bin/uid_pgbackrest.sh /opt/cpm/bin - -RUN chmod g=u /etc/passwd \ - && chmod g=u /etc/group \ - && chmod -R g=u /etc/pgbackrest \ - && rm -f /run/nologin - -RUN mkdir /.ssh && chown pgbackrest:pgbackrest /.ssh && chmod o+rwx /.ssh - -USER 2000 - -ENTRYPOINT ["/opt/cpm/bin/uid_pgbackrest.sh"] -VOLUME ["/sshd", "/backrestrepo" ] - -CMD ["pgo-backrest-repo.sh"] diff --git a/build/pgo-backrest/Dockerfile b/build/pgo-backrest/Dockerfile deleted file mode 100644 index 25adb20ee3..0000000000 --- a/build/pgo-backrest/Dockerfile +++ /dev/null @@ -1,31 +0,0 @@ -ARG BASEOS -ARG BASEVER -ARG PREFIX -FROM ${PREFIX}/pgo-base:${BASEOS}-${BASEVER} - -ARG PGVERSION -ARG BACKREST_VERSION -ARG PACKAGER -ARG DFSET - -LABEL name="pgo-backrest" \ - summary="Crunchy PostgreSQL Operator - pgBackRest" \ - description="pgBackRest image that is integrated for use with Crunchy Data's PostgreSQL Operator." - -RUN ${PACKAGER} -y install \ - --setopt=skip_missing_names_on_install=False \ - postgresql${PGVERSION}-server \ - crunchy-backrest-"${BACKREST_VERSION}" \ - && ${PACKAGER} -y clean all - -RUN mkdir -p /opt/cpm/bin /pgdata /backrestrepo && chown -R 26:26 /opt/cpm -ADD bin/pgo-backrest/ /opt/cpm/bin -ADD bin/uid_postgres.sh /opt/cpm/bin - -RUN chmod g=u /etc/passwd && \ - chmod g=u /etc/group - -USER 26 -ENTRYPOINT ["/opt/cpm/bin/uid_postgres.sh"] -VOLUME ["/pgdata","/backrestrepo"] -CMD ["/opt/cpm/bin/pgo-backrest"] diff --git a/cmd/pgo-backrest/main.go b/cmd/pgo-backrest/main.go deleted file mode 100644 index 3ea782ab35..0000000000 --- a/cmd/pgo-backrest/main.go +++ /dev/null @@ -1,154 +0,0 @@ -package main - -/* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. - 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. -*/ - -import ( - "os" - "strconv" - "strings" - - "github.com/crunchydata/postgres-operator/internal/kubeapi" - crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" - log "github.com/sirupsen/logrus" -) - -const backrestCommand = "pgbackrest" - -const backrestBackupCommand = `backup` -const backrestInfoCommand = `info` -const backrestStanzaCreateCommand = `stanza-create` -const containername = "database" -const repoTypeFlagS3 = "--repo1-type=s3" -const noRepoS3VerifyTLS = "--no-repo1-s3-verify-tls" - -func main() { - log.Info("pgo-backrest starts") - - debugFlag := os.Getenv("CRUNCHY_DEBUG") - if debugFlag == "true" { - log.SetLevel(log.DebugLevel) - log.Debug("debug flag set to true") - } else { - log.Info("debug flag set to false") - } - - Namespace := os.Getenv("NAMESPACE") - log.Debugf("setting NAMESPACE to %s", Namespace) - if Namespace == "" { - log.Error("NAMESPACE env var not set") - os.Exit(2) - } - - Command := os.Getenv("COMMAND") - log.Debugf("setting COMMAND to %s", Command) - if Command == "" { - log.Error("COMMAND env var not set") - os.Exit(2) - } - - CommandOpts := os.Getenv("COMMAND_OPTS") - log.Debugf("setting COMMAND_OPTS to %s", CommandOpts) - - PodName := os.Getenv("PODNAME") - log.Debugf("setting PODNAME to %s", PodName) - if PodName == "" { - log.Error("PODNAME env var not set") - os.Exit(2) - } - - RepoType := os.Getenv("PGBACKREST_REPO_TYPE") - log.Debugf("setting REPO_TYPE to %s", RepoType) - - // determine the setting of PGHA_PGBACKREST_LOCAL_S3_STORAGE - // we will discard the error and treat the value as "false" if it is not - // explicitly set - LocalS3Storage, _ := strconv.ParseBool(os.Getenv("PGHA_PGBACKREST_LOCAL_S3_STORAGE")) - log.Debugf("setting PGHA_PGBACKREST_LOCAL_S3_STORAGE to %v", LocalS3Storage) - - // parse the environment variable and store the appropriate boolean value - // we will discard the error and treat the value as "false" if it is not - // explicitly set - S3VerifyTLS, _ := strconv.ParseBool(os.Getenv("PGHA_PGBACKREST_S3_VERIFY_TLS")) - log.Debugf("setting PGHA_PGBACKREST_S3_VERIFY_TLS to %v", S3VerifyTLS) - - client, err := kubeapi.NewClient() - if err != nil { - panic(err) - } - - bashcmd := make([]string, 1) - bashcmd[0] = "bash" - cmdStrs := make([]string, 0) - - switch Command { - case crv1.PgtaskBackrestStanzaCreate: - log.Info("backrest stanza-create command requested") - cmdStrs = append(cmdStrs, backrestCommand) - cmdStrs = append(cmdStrs, backrestStanzaCreateCommand) - cmdStrs = append(cmdStrs, CommandOpts) - case crv1.PgtaskBackrestInfo: - log.Info("backrest info command requested") - cmdStrs = append(cmdStrs, backrestCommand) - cmdStrs = append(cmdStrs, backrestInfoCommand) - cmdStrs = append(cmdStrs, CommandOpts) - case crv1.PgtaskBackrestBackup: - log.Info("backrest backup command requested") - cmdStrs = append(cmdStrs, backrestCommand) - cmdStrs = append(cmdStrs, backrestBackupCommand) - cmdStrs = append(cmdStrs, CommandOpts) - default: - log.Error("unsupported backup command specified " + Command) - os.Exit(2) - } - - if LocalS3Storage { - firstCmd := cmdStrs - cmdStrs = append(cmdStrs, "&&") - cmdStrs = append(cmdStrs, strings.Join(firstCmd, " ")) - cmdStrs = append(cmdStrs, repoTypeFlagS3) - // pass in the flag to disable TLS verification, if set - // otherwise, maintain default behavior and verify TLS - if !S3VerifyTLS { - cmdStrs = append(cmdStrs, noRepoS3VerifyTLS) - } - log.Info("backrest command will be executed for both local and s3 storage") - } else if RepoType == "s3" { - cmdStrs = append(cmdStrs, repoTypeFlagS3) - // pass in the flag to disable TLS verification, if set - // otherwise, maintain default behavior and verify TLS - if !S3VerifyTLS { - cmdStrs = append(cmdStrs, noRepoS3VerifyTLS) - } - log.Info("s3 flag enabled for backrest command") - } - - log.Infof("command to execute is [%s]", strings.Join(cmdStrs, " ")) - - log.Infof("command is %s ", strings.Join(cmdStrs, " ")) - reader := strings.NewReader(strings.Join(cmdStrs, " ")) - output, stderr, err := kubeapi.ExecToPodThroughAPI(client.Config, client, bashcmd, containername, PodName, Namespace, reader) - if err != nil { - log.Info("output=[" + output + "]") - log.Info("stderr=[" + stderr + "]") - log.Error(err) - os.Exit(2) - } - log.Info("output=[" + output + "]") - log.Info("stderr=[" + stderr + "]") - - log.Info("pgo-backrest ends") - -} diff --git a/cmd/pgo-scheduler/scheduler/pgbackrest.go b/cmd/pgo-scheduler/scheduler/pgbackrest.go index eba3048da8..715b48dd57 100644 --- a/cmd/pgo-scheduler/scheduler/pgbackrest.go +++ b/cmd/pgo-scheduler/scheduler/pgbackrest.go @@ -115,7 +115,7 @@ func (b BackRestBackupJob) Run() { return } - selector := fmt.Sprintf("%s=%s,pgo-backrest-repo=true", config.LABEL_PG_CLUSTER, b.cluster) + selector := fmt.Sprintf("%s=%s,crunchy-pgbackrest-repo=true", config.LABEL_PG_CLUSTER, b.cluster) pods, err := clientset.CoreV1().Pods(b.namespace).List(ctx, metav1.ListOptions{LabelSelector: selector}) if err != nil { contextLogger.WithFields(log.Fields{ @@ -142,7 +142,7 @@ func (b BackRestBackupJob) Run() { backupOptions: fmt.Sprintf("--type=%s %s", b.backupType, b.options), stanza: b.stanza, storageType: b.storageType, - imagePrefix: cluster.Spec.PGOImagePrefix, + imagePrefix: cluster.Spec.CCPImagePrefix, } _, err = clientset.CrunchydataV1().Pgtasks(b.namespace). diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json index 82b326c7cf..dddc0b14d9 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json @@ -31,7 +31,7 @@ "serviceAccountName": "pgo-backrest", "containers": [{ "name": "backrest", - "image": "{{.PGOImagePrefix}}/pgo-backrest:{{.PGOImageTag}}", + "image": "{{.CCPImagePrefix}}/crunchy-pgbackrest:{{.CCPImageTag}}", "volumeMounts": [ {{.PgbackrestRestoreVolumeMounts}} ], @@ -39,6 +39,9 @@ "name": "COMMAND", "value": "{{.Command}}" }, { + "name": "MODE", + "value": "pgbackrest" + },{ "name": "COMMAND_OPTS", "value": "{{.CommandOpts}}" }, { diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json index 5f9e5d5049..dba4a3d8d7 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json @@ -48,7 +48,7 @@ "serviceAccountName": "pgo-default", "containers": [{ "name": "database", - "image": "{{.PGOImagePrefix}}/pgo-backrest-repo:{{.PGOImageTag}}", + "image": "{{.CCPImagePrefix}}/crunchy-pgbackrest-repo:{{.CCPImageTag}}", "ports": [{ "containerPort": {{.SshdPort}}, "protocol": "TCP" @@ -57,12 +57,12 @@ "env": [ {{.PgbackrestS3EnvVars}} { - "name": "PGBACKREST_STANZA", - "value": "{{.PgbackrestStanza}}" + "name": "MODE", + "value": "pgbackrest-repo" }, { - "name": "SSHD_PORT", - "value": "{{.SshdPort}}" + "name": "PGBACKREST_STANZA", + "value": "{{.PgbackrestStanza}}" }, { "name": "PGBACKREST_DB_PATH", diff --git a/installers/olm/postgresoperator.csv.images.yaml b/installers/olm/postgresoperator.csv.images.yaml index 429a882893..97aa48299c 100644 --- a/installers/olm/postgresoperator.csv.images.yaml +++ b/installers/olm/postgresoperator.csv.images.yaml @@ -4,8 +4,8 @@ - { name: PGO_IMAGE_PREFIX, value: '${PGO_IMAGE_PREFIX}' } - { name: PGO_IMAGE_TAG, value: '${PGO_IMAGE_TAG}' } -- { name: RELATED_IMAGE_PGO_BACKREST, value: '${PGO_IMAGE_PREFIX}/pgo-backrest:${PGO_IMAGE_TAG}' } -- { name: RELATED_IMAGE_PGO_BACKREST_REPO, value: '${PGO_IMAGE_PREFIX}/pgo-backrest-repo:${PGO_IMAGE_TAG}' } +- { name: RELATED_IMAGE_PGO_BACKREST, value: '${CCP_IMAGE_PREFIX}/crunchy-pgbackrest:${CCP_IMAGE_TAG}' } +- { name: RELATED_IMAGE_PGO_BACKREST_REPO, value: '${CCP_IMAGE_PREFIX}/crunchy-pgbackrest-repo:${CCP_IMAGE_TAG}' } - { name: RELATED_IMAGE_PGO_CLIENT, value: '${PGO_IMAGE_PREFIX}/pgo-client:${PGO_IMAGE_TAG}' } - { name: RELATED_IMAGE_PGO_RMDATA, value: '${PGO_IMAGE_PREFIX}/pgo-rmdata:${PGO_IMAGE_TAG}' } - { name: RELATED_IMAGE_CRUNCHY_POSTGRES_EXPORTER, value: '${PGO_IMAGE_PREFIX}/crunchy-postgres-exporter:${PGO_IMAGE_TAG}' } diff --git a/internal/apiserver/backrestservice/backrestimpl.go b/internal/apiserver/backrestservice/backrestimpl.go index 8ffd051857..3206d7fcee 100644 --- a/internal/apiserver/backrestservice/backrestimpl.go +++ b/internal/apiserver/backrestservice/backrestimpl.go @@ -219,7 +219,7 @@ func CreateBackup(request *msgs.CreateBackrestBackupRequest, ns, pgouser string) getBackupParams( cluster.ObjectMeta.Labels[config.LABEL_PG_CLUSTER_IDENTIFIER], clusterName, taskName, crv1.PgtaskBackrestBackup, podname, "database", - util.GetValueOrDefault(cluster.Spec.PGOImagePrefix, apiserver.Pgo.Pgo.PGOImagePrefix), + util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, apiserver.Pgo.Cluster.CCPImagePrefix), request.BackupOpts, request.BackrestStorageType, operator.GetS3VerifyTLSSetting(cluster), jobName, ns, pgouser), metav1.CreateOptions{}, ) diff --git a/internal/config/annotations.go b/internal/config/annotations.go index 7cf97b96ee..db8482fe0a 100644 --- a/internal/config/annotations.go +++ b/internal/config/annotations.go @@ -48,7 +48,7 @@ const ( // ANNOTATION_S3_VERIFY_TLS is for storing the setting that determines whether or not TLS should // be used to access a pgBackRest repository ANNOTATION_S3_VERIFY_TLS = "s3-verify-tls" - // ANNOTATION_S3_BUCKET is for storing the SSHD port used by the pgBackRest repository + // ANNOTATION_SSHD_PORT is for storing the SSHD port used by the pgBackRest repository // service in a cluster ANNOTATION_SSHD_PORT = "sshd-port" // ANNOTATION_SUPPLEMENTAL_GROUPS is for storing the supplemental groups used with a cluster diff --git a/internal/config/images.go b/internal/config/images.go index 71c0af7c1c..2811e927fc 100644 --- a/internal/config/images.go +++ b/internal/config/images.go @@ -17,8 +17,8 @@ package config // a list of container images that are available const ( - CONTAINER_IMAGE_PGO_BACKREST = "pgo-backrest" - CONTAINER_IMAGE_PGO_BACKREST_REPO = "pgo-backrest-repo" + CONTAINER_IMAGE_PGO_BACKREST = "crunchy-pgbackrest" + CONTAINER_IMAGE_PGO_BACKREST_REPO = "crunchy-pgbackrest-repo" CONTAINER_IMAGE_PGO_CLIENT = "pgo-client" CONTAINER_IMAGE_PGO_RMDATA = "pgo-rmdata" CONTAINER_IMAGE_CRUNCHY_ADMIN = "crunchy-admin" diff --git a/internal/operator/backrest/backup.go b/internal/operator/backrest/backup.go index 2351fe0b2b..89f4b8a29f 100644 --- a/internal/operator/backrest/backup.go +++ b/internal/operator/backrest/backup.go @@ -48,8 +48,8 @@ type backrestJobTemplateFields struct { CommandOpts string PITRTarget string PodName string - PGOImagePrefix string - PGOImageTag string + CCPImagePrefix string + CCPImageTag string SecurityContext string PgbackrestStanza string PgbackrestDBPath string @@ -80,8 +80,8 @@ func Backrest(namespace string, clientset kubernetes.Interface, task *crv1.Pgtas Command: cmd, CommandOpts: task.Spec.Parameters[config.LABEL_BACKREST_OPTS], PITRTarget: "", - PGOImagePrefix: util.GetValueOrDefault(task.Spec.Parameters[config.LABEL_IMAGE_PREFIX], operator.Pgo.Pgo.PGOImagePrefix), - PGOImageTag: operator.Pgo.Pgo.PGOImageTag, + CCPImagePrefix: util.GetValueOrDefault(task.Spec.Parameters[config.LABEL_IMAGE_PREFIX], operator.Pgo.Cluster.CCPImagePrefix), + CCPImageTag: operator.Pgo.Cluster.CCPImageTag, PgbackrestStanza: task.Spec.Parameters[config.LABEL_PGBACKREST_STANZA], PgbackrestDBPath: task.Spec.Parameters[config.LABEL_PGBACKREST_DB_PATH], PgbackrestRepoPath: task.Spec.Parameters[config.LABEL_PGBACKREST_REPO_PATH], @@ -200,7 +200,7 @@ func CreateBackup(clientset pgo.Interface, namespace, clusterName, podName strin spec.Parameters[config.LABEL_CONTAINER_NAME] = "database" // pass along the appropriate image prefix for the backup task // this will be used by the associated backrest job - spec.Parameters[config.LABEL_IMAGE_PREFIX] = util.GetValueOrDefault(cluster.Spec.PGOImagePrefix, operator.Pgo.Pgo.PGOImagePrefix) + spec.Parameters[config.LABEL_IMAGE_PREFIX] = util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, operator.Pgo.Cluster.CCPImagePrefix) spec.Parameters[config.LABEL_BACKREST_COMMAND] = crv1.PgtaskBackrestBackup spec.Parameters[config.LABEL_BACKREST_OPTS] = backupOpts spec.Parameters[config.LABEL_BACKREST_STORAGE_TYPE] = cluster.Spec.UserLabels[config.LABEL_BACKREST_STORAGE_TYPE] diff --git a/internal/operator/backrest/repo.go b/internal/operator/backrest/repo.go index e53427fd1d..6266afd510 100644 --- a/internal/operator/backrest/repo.go +++ b/internal/operator/backrest/repo.go @@ -44,8 +44,8 @@ var s3RepoTypeRegex = regexp.MustCompile(`--repo-type=["']?s3["']?`) type RepoDeploymentTemplateFields struct { SecurityContext string - PGOImagePrefix string - PGOImageTag string + CCPImagePrefix string + CCPImageTag string ContainerResources string BackrestRepoClaimName string SshdSecretsName string @@ -229,8 +229,8 @@ func getRepoDeploymentFields(clientset kubernetes.Interface, cluster *crv1.Pgclu namespace := cluster.GetNamespace() repoFields := RepoDeploymentTemplateFields{ - PGOImagePrefix: util.GetValueOrDefault(cluster.Spec.PGOImagePrefix, operator.Pgo.Pgo.PGOImagePrefix), - PGOImageTag: operator.Pgo.Pgo.PGOImageTag, + CCPImagePrefix: util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, operator.Pgo.Cluster.CCPImagePrefix), + CCPImageTag: operator.Pgo.Cluster.CCPImageTag, ContainerResources: operator.GetResourcesJSON(cluster.Spec.BackrestResources, cluster.Spec.BackrestLimits), BackrestRepoClaimName: fmt.Sprintf(util.BackrestRepoPVCName, cluster.Name), SshdSecretsName: fmt.Sprintf(util.BackrestRepoSecretName, cluster.Name), diff --git a/internal/operator/backrest/restore.go b/internal/operator/backrest/restore.go index e6ce666f22..8107bcc09f 100644 --- a/internal/operator/backrest/restore.go +++ b/internal/operator/backrest/restore.go @@ -54,8 +54,8 @@ type BackrestRestoreJobTemplateFields struct { WorkflowID string ToClusterPVCName string SecurityContext string - PGOImagePrefix string - PGOImageTag string + CCPImagePrefix string + CCPImageTag string CommandOpts string PITRTarget string PgbackrestStanza string diff --git a/internal/operator/backrest/stanza.go b/internal/operator/backrest/stanza.go index 186996abd0..2607eb7a6e 100644 --- a/internal/operator/backrest/stanza.go +++ b/internal/operator/backrest/stanza.go @@ -89,10 +89,10 @@ func StanzaCreate(namespace, clusterName string, clientset kubeapi.Interface) { spec.Parameters[config.LABEL_JOB_NAME] = jobName spec.Parameters[config.LABEL_PG_CLUSTER] = clusterName spec.Parameters[config.LABEL_POD_NAME] = podName - spec.Parameters[config.LABEL_CONTAINER_NAME] = "pgo-backrest-repo" + spec.Parameters[config.LABEL_CONTAINER_NAME] = "crunchy-pgbackrest-repo" // pass along the appropriate image prefix for the backup task // this will be used by the associated backrest job - spec.Parameters[config.LABEL_IMAGE_PREFIX] = util.GetValueOrDefault(cluster.Spec.PGOImagePrefix, operator.Pgo.Pgo.PGOImagePrefix) + spec.Parameters[config.LABEL_IMAGE_PREFIX] = util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, operator.Pgo.Cluster.CCPImagePrefix) spec.Parameters[config.LABEL_BACKREST_COMMAND] = crv1.PgtaskBackrestStanzaCreate // Handle stanza creation for a standby cluster, which requires some additional consideration. From 5c16b645e18e6a86792235e3e07c9719518c8556 Mon Sep 17 00:00:00 2001 From: andrewlecuyer <43458182+andrewlecuyer@users.noreply.github.com> Date: Tue, 8 Dec 2020 15:00:14 -0600 Subject: [PATCH 029/276] No post-failover backups for standby clusters A post-failover backup is now only triggered for non-standby clusters. Therefore, if a failover occurs within a standby cluster, and automatic backup will no longer be run. Issue: [ch9912] Issue: #2102 --- internal/controller/pod/promotionhandler.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/controller/pod/promotionhandler.go b/internal/controller/pod/promotionhandler.go index dcdcf48590..2dbc34ab6f 100644 --- a/internal/controller/pod/promotionhandler.go +++ b/internal/controller/pod/promotionhandler.go @@ -63,7 +63,8 @@ func (c *Controller) handlePostgresPodPromotion(newPod *apiv1.Pod, cluster crv1. } } - if cluster.Status.State == crv1.PgclusterStateInitialized { + // create a post-failover backup if not a standby cluster + if !cluster.Spec.Standby && cluster.Status.State == crv1.PgclusterStateInitialized { if err := cleanAndCreatePostFailoverBackup(c.Client, cluster.Name, newPod.Namespace); err != nil { log.Error(err) From fae52d31d6bd7c3d24667a9c1aa2424f135b8520 Mon Sep 17 00:00:00 2001 From: Joseph Mckulka <16840147+jmckulk@users.noreply.github.com> Date: Tue, 8 Dec 2020 17:25:16 -0500 Subject: [PATCH 030/276] Revert label change As part of the compaction changes some labels and label checks were changed. This pr reverts these changes. The `pgo-scheduler` code was updated to check for a `crunchy-pgbackrest-repo` label instead of the `pgo-backrest-repo` label. The deployment templates were not updated to use the updated label so the scheduler would fail to create a backup job when scheduling a backup. the pgo.sqlrunner template was updated to have the `sqlrunner` label instead of `pgo-sqlrunner` --- cmd/pgo-scheduler/scheduler/pgbackrest.go | 2 +- .../files/pgo-configs/pgo.sqlrunner-template.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/pgo-scheduler/scheduler/pgbackrest.go b/cmd/pgo-scheduler/scheduler/pgbackrest.go index 715b48dd57..710e1f12d2 100644 --- a/cmd/pgo-scheduler/scheduler/pgbackrest.go +++ b/cmd/pgo-scheduler/scheduler/pgbackrest.go @@ -115,7 +115,7 @@ func (b BackRestBackupJob) Run() { return } - selector := fmt.Sprintf("%s=%s,crunchy-pgbackrest-repo=true", config.LABEL_PG_CLUSTER, b.cluster) + selector := fmt.Sprintf("%s=%s,pgo-backrest-repo=true", config.LABEL_PG_CLUSTER, b.cluster) pods, err := clientset.CoreV1().Pods(b.namespace).List(ctx, metav1.ListOptions{LabelSelector: selector}) if err != nil { contextLogger.WithFields(log.Fields{ diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json index a301df048f..56f55dd35e 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json @@ -5,7 +5,7 @@ "name": "{{.JobName}}", "labels": { "vendor": "crunchydata", - "sqlrunner": "true", + "pgo-sqlrunner": "true", "pg-cluster": "{{.ClusterName}}" } }, @@ -15,7 +15,7 @@ "name": "{{.JobName}}", "labels": { "vendor": "crunchydata", - "sqlrunner": "true", + "pgo-sqlrunner": "true", "pg-cluster": "{{.ClusterName}}" } }, From 83aef439895a9de4aa52fb9383c4e4292aa9feb5 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sat, 28 Nov 2020 16:14:17 -0500 Subject: [PATCH 031/276] Refactor wait for deployment function in context of a cluster This was originally written for the pgAdmin 4 integration, but can serve multiple purposes for some of the advanced updating logic. --- internal/operator/cluster/clusterlogic.go | 25 +++++++++++++++++ internal/operator/cluster/pgadmin.go | 34 ++--------------------- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/internal/operator/cluster/clusterlogic.go b/internal/operator/cluster/clusterlogic.go index f6ddf85fca..f0c34c2007 100644 --- a/internal/operator/cluster/clusterlogic.go +++ b/internal/operator/cluster/clusterlogic.go @@ -798,3 +798,28 @@ func ScaleClusterDeployments(clientset kubernetes.Interface, cluster crv1.Pgclus } return } + +// waitFotDeploymentReady waits for a deployment to be ready, or times out +func waitForDeploymentReady(clientset kubernetes.Interface, namespace, deploymentName string, periodSecs, timeoutSecs time.Duration) error { + ctx := context.TODO() + + // set up the timer and timeout + // first, ensure that there is an available Pod + timeout := time.After(timeoutSecs) + tick := time.NewTicker(periodSecs) + defer tick.Stop() + + for { + select { + case <-timeout: + return fmt.Errorf("readiness timeout reached for deployment %q", deploymentName) + case <-tick.C: + // check to see if the deployment is ready + if d, err := clientset.AppsV1().Deployments(namespace).Get(ctx, deploymentName, metav1.GetOptions{}); err != nil { + log.Warn(err) + } else if d.Status.Replicas == d.Status.ReadyReplicas { + return nil + } + } + } +} diff --git a/internal/operator/cluster/pgadmin.go b/internal/operator/cluster/pgadmin.go index c49462e4d0..529bba6f13 100644 --- a/internal/operator/cluster/pgadmin.go +++ b/internal/operator/cluster/pgadmin.go @@ -20,7 +20,6 @@ import ( "context" "encoding/base64" "encoding/json" - "errors" "fmt" weakrand "math/rand" "os" @@ -69,8 +68,8 @@ const pgAdminDeploymentFormat = "%s-pgadmin" const initPassLen = 20 const ( - deployTimeout = 60 - pollInterval = 3 + deployTimeout = 60 * time.Second + pollInterval = 3 * time.Second ) // AddPgAdmin contains the various functions that are used to add a pgAdmin @@ -159,7 +158,7 @@ func AddPgAdminFromPgTask(clientset kubeapi.Interface, restconfig *rest.Config, } deployName := fmt.Sprintf(pgAdminDeploymentFormat, clusterName) - if err := waitForDeploymentReady(clientset, namespace, deployName, deployTimeout, pollInterval); err != nil { + if err := waitForDeploymentReady(clientset, namespace, deployName, pollInterval, deployTimeout); err != nil { log.Error(err) } @@ -470,30 +469,3 @@ func publishPgAdminEvent(eventType string, task *crv1.Pgtask) { log.Error(err.Error()) } } - -// waitFotDeploymentReady waits for a deployment to be ready, or times out -func waitForDeploymentReady(clientset kubernetes.Interface, namespace, deploymentName string, timeoutSecs, periodSecs time.Duration) error { - ctx := context.TODO() - timeout := time.After(timeoutSecs * time.Second) - tick := time.NewTicker(periodSecs * time.Second) - defer tick.Stop() - - // loop until the timeout is met, or that all the replicas are ready - for { - select { - case <-timeout: - return errors.New(fmt.Sprintf("Timed out waiting for deployment to become ready: [%s]", deploymentName)) - case <-tick.C: - if deployment, err := clientset.AppsV1().Deployments(namespace).Get(ctx, deploymentName, metav1.GetOptions{}); err != nil { - // if there is an error, log it but continue through the loop - log.Error(err) - } else { - // check to see if the deployment status has succeed...if so, break out - // of the loop - if deployment.Status.ReadyReplicas == *deployment.Spec.Replicas { - return nil - } - } - } - } -} From beb3c9dc147856a67321fa8bcbdf999958ef63c0 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sat, 28 Nov 2020 15:27:26 -0500 Subject: [PATCH 032/276] Introduce rolling update interface for PostgreSQL clusters A rolling update of a PostgreSQL cluster involves applying any updates that may require downtime to each replica within a PostgreSQL cluster, followed by the promotion to a replica deemed suitable to be a primary, followed by the update being applied to the former primary. This commit introduces an interface to perform this exact behavior, by allowing for any updates to the Deployments of PostgreSQL instances to have any updates applied in a rolling fashion. Issue: [ch9881] --- .../architecture/high-availability/_index.md | 55 +++ internal/operator/cluster/rolling.go | 331 ++++++++++++++++++ internal/util/failover.go | 14 +- 3 files changed, 394 insertions(+), 6 deletions(-) create mode 100644 internal/operator/cluster/rolling.go diff --git a/docs/content/architecture/high-availability/_index.md b/docs/content/architecture/high-availability/_index.md index c5f05eaf96..950b150105 100644 --- a/docs/content/architecture/high-availability/_index.md +++ b/docs/content/architecture/high-availability/_index.md @@ -276,3 +276,58 @@ The Node Affinity only uses the `preferred` scheduling strategy (similar to what is described in the Pod Anti-Affinity section above), so if a Pod cannot be scheduled to a particular Node matching the label, it will be scheduled to a different Node. + +## Rolling Updates + +During the lifecycle of a PostgreSQL cluster, there are certain events that may +require a planned restart, such as an update to a "restart required" PostgreSQL +configuration setting (e.g. [`shared_buffers`](https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-SHARED-BUFFERS)) +or a change to a Kubernetes Deployment template (e.g. [changing the memory request]({{< relref "tutorial/customize-cluster.md">}}#customize-cpu-memory)). Restarts can be disruptive in a high availability deployment, which is +why many setups employ a ["rolling update" strategy](https://kubernetes.io/docs/tutorials/kubernetes-basics/update/update-intro/) +(aka a "rolling restart") to minimize or eliminate downtime during a planned +restart. + +Because PostgreSQL is a stateful application, a simple rolling restart strategy +will not work: PostgreSQL needs to ensure that there is a primary available that +can accept reads and writes. This requires following a method that will minimize +the amount of downtime when the primary is taken offline for a restart. + +The PostgreSQL Operator provides a mechanism for rolling updates implicitly on +certain operations that change the Deployment templates (e.g. memory updates, +CPU updates, adding tablespaces, modifiny annotations) and explicitly through +the [`pgo restart`]({{< relref "pgo-client/reference/pgo_restart.md">}}) +command with the `--rolling` flag. The PostgreSQL Operator uses the following +algorithm to perform the rolling restart to minimize any potential +interruptions: + +1. Each replica is updated in sequential order. This follows the following +process: + + 1. The replica is explicitly shut down to ensure any outstanding changes are + flushed to disk. + + 2. If requested, the PostgreSQL Operator will apply any changes to the + Deployment. + + 3. The replica is brought back online. The PostgreSQL Operator waits for the + replica to become available before it proceeds to the next replica. + +2. The above steps are repeated until all of the replicas are restarted. + +3. A controlled switchover is performed. The PostgreSQL Operator determines +which replica is the best candidate to become the new primary. It then demotes +the primary to become a replica and promotes the best candidate to become the +new primary. + +4. The former primary follows a process similar to what is described in step 1. + +The downtime is thus constrained to the amount of time the switchover takes. + +A rolling update strategy will be used if any of the following changes are made +to a PostgreSQL cluster, either through the `pgo update` command or from a +modification to the custom resource: + +- Memory resource adjustments +- CPU resource adjustments +- Custom annotation changes +- Tablespace additions diff --git a/internal/operator/cluster/rolling.go b/internal/operator/cluster/rolling.go new file mode 100644 index 0000000000..9f5351d7b0 --- /dev/null +++ b/internal/operator/cluster/rolling.go @@ -0,0 +1,331 @@ +package cluster + +/* + Copyright 2020 Crunchy Data Solutions, Inc. + 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. +*/ + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/crunchydata/postgres-operator/internal/config" + "github.com/crunchydata/postgres-operator/internal/kubeapi" + "github.com/crunchydata/postgres-operator/internal/operator" + "github.com/crunchydata/postgres-operator/internal/util" + crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" + log "github.com/sirupsen/logrus" + appsv1 "k8s.io/api/apps/v1" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" +) + +type deploymentType int + +const ( + deploymentTypePrimary deploymentType = iota + deploymentTypeReplica +) + +const ( + rollingUpdatePeriod = 4 * time.Second + rollingUpdateTimeout = 60 * time.Second +) + +// RollingUpdate performs a type of "rolling update" on a series of Deployments +// of a PostgreSQL cluster in an attempt to minimize downtime. +// +// The functions take a function that serves to update the contents of a +// Deployment. +// +// The rolling update is performed as such: +// +// 1. Each replica is updated. A replica is shut down and changes are applied +// The Operator waits until the replica is back online (and/or a time period) +// And moves on to the next one +// 2. A controlled switchover is performed. The Operator chooses the best +// candidate replica for the switch over. +// 3. The former primary is then shut down and updated. +// +// If this is not a HA cluster, then the Deployment is just singly restarted +// +// Erroring during this process can be fun. If an error occurs within the middle +// of a rolling update, in order to avoid placing the cluster in an +// indeterminate state, most errors are just logged for later troubleshooting +func RollingUpdate(clientset kubernetes.Interface, restConfig *rest.Config, cluster *crv1.Pgcluster, + updateFunc func(*crv1.Pgcluster, *appsv1.Deployment) error) error { + log.Debugf("rolling update for cluster %q", cluster.Name) + + // we need to determine which deployments are replicas and which is the + // primary. Note, that based on external factors, this can change during the + // execution of this function, so this is our best guess at the time of the + // rolling update being performed. + // + // Given the craziness of a distributed world, we may even unearth two + // primaries, or no primaries! So we will need to gracefully handle that as + // well + // + // We will get this through the Pod list as the role label is on the Pod + instances, err := generateDeploymentTypeMap(clientset, cluster) + // If we fail to generate the deployment type map, we just have to fail here. + // We can't do any updates + if err != nil { + return err + } + + // go through all of the replicas and perform the modifications + for i := range instances[deploymentTypeReplica] { + deployment := instances[deploymentTypeReplica][i] + + // Try to apply the update. If it returns an error during the process, + // continue on to the next replica + if err := applyUpdateToPostgresInstance(clientset, restConfig, cluster, deployment, updateFunc); err != nil { + log.Error(err) + continue + } + + // Ensure that the replica comes back up and can be connected to, otherwise + // keep moving on. This involves waiting for the Deployment to come back + // up... + if err := waitForDeploymentReady(clientset, deployment.Namespace, deployment.Name, + rollingUpdatePeriod, rollingUpdateTimeout); err != nil { + log.Warn(err) + } + + // ...followed by wiating for the PostgreSQL instance to come back up + if err := waitForPostgresInstance(clientset, restConfig, cluster, deployment, + rollingUpdatePeriod, rollingUpdateTimeout); err != nil { + log.Warn(err) + } + } + + // if there is at least one replica and only one primary, perform a controlled + // switchover. + // + // if multiple primaries were found, we don't know how we would want to + // properly switch over, so we will let Patroni make the decision in this case + // as part of an uncontrolled failover. At this point, we should have eligible + // replicas that have the updated Deployment state. + if len(instances[deploymentTypeReplica]) > 0 && len(instances[deploymentTypePrimary]) == 1 { + // if the switchover fails, warn that it failed but continue on + if err := switchover(clientset, restConfig, cluster); err != nil { + log.Warnf("switchover failed: %s", err.Error()) + } + } + + // finally, go through the list of primaries (which should only be one...) + // and apply the update. At this point we do not need to wait for anything, + // as we should have either already promoted a new primary, or this is a + // single instance cluster + for i := range instances[deploymentTypePrimary] { + if err := applyUpdateToPostgresInstance(clientset, restConfig, cluster, + instances[deploymentTypePrimary][i], updateFunc); err != nil { + log.Error(err) + } + } + + return nil +} + +// applyUpdateToPostgresInstance performs an update on an individual PostgreSQL +// instance. It first ensures that the update can be applied. If it can, it will +// safely turn of the PostgreSQL instance before modifying the Deployment +// template. +func applyUpdateToPostgresInstance(clientset kubernetes.Interface, restConfig *rest.Config, + cluster *crv1.Pgcluster, deployment appsv1.Deployment, + updateFunc func(*crv1.Pgcluster, *appsv1.Deployment) error) error { + ctx := context.TODO() + + // apply any updates, if they cannot be applied, then return an error here + if err := updateFunc(cluster, &deployment); err != nil { + return err + } + + // Before applying the update, we want to explicitly stop PostgreSQL on each + // instance. This prevents PostgreSQL from having to boot up in crash + // recovery mode. + // + // If an error is returned, warn, but proceed with the function + if err := stopPostgreSQLInstance(clientset, restConfig, deployment); err != nil { + log.Warn(err) + } + + // Perform the update. + _, err := clientset.AppsV1().Deployments(deployment.Namespace). + Update(ctx, &deployment, metav1.UpdateOptions{}) + + return err +} + +// generateDeploymentTypeMap takes a list of Deployments and determines what +// they represent: a primary (hopefully only one) or replicas +func generateDeploymentTypeMap(clientset kubernetes.Interface, cluster *crv1.Pgcluster) (map[deploymentType][]appsv1.Deployment, error) { + ctx := context.TODO() + + // get a list of all of the instance deployments for the cluster + deployments, err := operator.GetInstanceDeployments(clientset, cluster) + if err != nil { + return nil, err + } + + options := metav1.ListOptions{ + LabelSelector: fields.AndSelectors( + fields.OneTermEqualSelector(config.LABEL_PG_CLUSTER, cluster.Name), + fields.OneTermEqualSelector(config.LABEL_PG_DATABASE, config.LABEL_TRUE), + ).String(), + } + + pods, err := clientset.CoreV1().Pods(cluster.Namespace).List(ctx, options) + // if we can't find any of the Pods, we can't make the proper determiniation + if err != nil { + return nil, err + } + + // go through each Deployment and make a determination about its type. If we + // ultimately cannot do that, treat the deployment as a "replica" + instances := map[deploymentType][]appsv1.Deployment{ + deploymentTypePrimary: {}, + deploymentTypeReplica: {}, + } + + for i, deployment := range deployments.Items { + for _, pod := range pods.Items { + // if the Pod doesn't match, continue + if deployment.Name != pod.ObjectMeta.GetLabels()[config.LABEL_DEPLOYMENT_NAME] { + continue + } + + // found matching Pod, determine if it's a primary or replica + if pod.ObjectMeta.GetLabels()[config.LABEL_PGHA_ROLE] == config.LABEL_PGHA_ROLE_PRIMARY { + instances[deploymentTypePrimary] = append(instances[deploymentTypePrimary], deployments.Items[i]) + } else { + instances[deploymentTypeReplica] = append(instances[deploymentTypeReplica], deployments.Items[i]) + } + + // we found the (or at least a) matching Pod, so we can break the loop now + break + } + } + + return instances, nil +} + +// generatePostgresReadyCommand creates the command used to test if a PostgreSQL +// instance is ready +func generatePostgresReadyCommand(port string) []string { + return []string{"pg_isready", "-p", port} +} + +// generatePostgresSwitchoverCommand creates the command that is used to issue +// a switchover (demote a primary, promote a replica). Takes the name of the +// cluster; Patroni will choose the best candidate to switchover to +func generatePostgresSwitchoverCommand(clusterName string) []string { + return []string{"patronictl", "switchover", "--force", clusterName} +} + +// switchover performs a controlled switchover within a PostgreSQL cluster, i.e. +// demoting a primary and promoting a replica. The method works as such: +// +// 1. The function looks for all available replicas as well as the current +// primary. We look up the primary for convenience to avoid various API calls +// +// 2. We then search over the list to find both a primary and a suitable +// candidate for promotion. A candidate is suitable if: +// - It is on the latest timeline +// - It has the least amount of replication lag +// +// This is done to limit the risk of data loss. +// +// If either a primary or candidate is **not** found, we do not switch over. +// +// 3. If all of the above works successfully, a switchover is attempted. +func switchover(clientset kubernetes.Interface, restConfig *rest.Config, cluster *crv1.Pgcluster) error { + // we want to find a Pod to execute the switchover command on, i.e. the + // primary + pod, err := util.GetPrimaryPod(clientset, cluster) + if err != nil { + return err + } + + // good to generally log which instances are being used in the switchover + log.Infof("controlled switchover started for cluster %q", cluster.Name) + + cmd := generatePostgresSwitchoverCommand(cluster.Name) + if _, stderr, err := kubeapi.ExecToPodThroughAPI(restConfig, clientset, + cmd, "database", pod.Name, cluster.Namespace, nil); err != nil { + return fmt.Errorf(stderr) + } + + log.Infof("controlled switchover completed for cluster %q", cluster.Name) + + // and that's all + return nil +} + +// waitForPostgresInstance waits for a PostgreSQL instance within a Pod is ready +// to accept connections +func waitForPostgresInstance(clientset kubernetes.Interface, restConfig *rest.Config, + cluster *crv1.Pgcluster, deployment appsv1.Deployment, periodSecs, timeoutSecs time.Duration) error { + ctx := context.TODO() + + // try to find the Pod that should be exec'd into + options := metav1.ListOptions{ + FieldSelector: fields.OneTermEqualSelector("status.phase", string(v1.PodRunning)).String(), + LabelSelector: fields.AndSelectors( + fields.OneTermEqualSelector(config.LABEL_PG_CLUSTER, cluster.Name), + fields.OneTermEqualSelector(config.LABEL_PG_DATABASE, config.LABEL_TRUE), + fields.OneTermEqualSelector(config.LABEL_DEPLOYMENT_NAME, deployment.Name), + ).String(), + } + pods, err := clientset.CoreV1().Pods(deployment.Namespace).List(ctx, options) + + // if the Pod selection errors, we can't really proceed + if err != nil { + return fmt.Errorf("could not find pods to check postgres instance readiness: %w", err) + } else if len(pods.Items) == 0 { + return fmt.Errorf("could not find any postgres pods") + } + + // get the first pod...we'll just have to presume this is the active primary + // as we've done all we good to narrow it down at this point + pod := pods.Items[0] + cmd := generatePostgresReadyCommand(cluster.Spec.Port) + + // set up the timer and timeout + // first, ensure that there is an available Pod + timeout := time.After(timeoutSecs) + tick := time.NewTicker(periodSecs) + defer tick.Stop() + + for { + select { + case <-timeout: + return fmt.Errorf("readiness timeout reached for start up of cluster %q instance %q", cluster.Name, deployment.Name) + case <-tick.C: + // check to see if PostgreSQL is ready to accept connections + s, _, _ := kubeapi.ExecToPodThroughAPI(restConfig, clientset, + cmd, "database", pod.Name, pod.Namespace, nil) + + // really we should find a way to get the exit code in the future, but + // in the interim... + if strings.Contains(s, "accepting connections") { + return nil + } + } + } +} diff --git a/internal/util/failover.go b/internal/util/failover.go index c17cd556ca..a4abba76f6 100644 --- a/internal/util/failover.go +++ b/internal/util/failover.go @@ -40,6 +40,7 @@ type InstanceReplicationInfo struct { Status string Timeline int PendingRestart bool + PodName string Role string } @@ -80,10 +81,10 @@ const ( // instanceReplicationInfoTypePrimaryStandby is the label used by Patroni to indicate that an // instance is indeed a primary PostgreSQL instance, specifically within a standby cluster instanceReplicationInfoTypePrimaryStandby = "Standby Leader" - // instanceRolePrimary indicates that an instance is a primary - instanceRolePrimary = "primary" - // instanceRoleReplica indicates that an instance is a replica - instanceRoleReplica = "replica" + // InstanceRolePrimary indicates that an instance is a primary + InstanceRolePrimary = "primary" + // InstanceRoleReplica indicates that an instance is a replica + InstanceRoleReplica = "replica" // instanceRoleUnknown indicates that an instance is of an unknown typ instanceRoleUnknown = "unknown" // instanceStatusUnavailable indicates an instance is unavailable @@ -266,9 +267,9 @@ func ReplicationStatus(request ReplicationStatusRequest, includePrimary, include // determine the role of the instnace switch rawInstance.Type { default: - role = instanceRoleReplica + role = InstanceRoleReplica case instanceReplicationInfoTypePrimary, instanceReplicationInfoTypePrimaryStandby: - role = instanceRolePrimary + role = InstanceRolePrimary } // set up the instance that will be returned @@ -280,6 +281,7 @@ func ReplicationStatus(request ReplicationStatusRequest, includePrimary, include Name: instanceInfoMap[rawInstance.PodName].name, Node: instanceInfoMap[rawInstance.PodName].node, PendingRestart: rawInstance.PendingRestart == "*", + PodName: rawInstance.PodName, } // update the instance info if the instance is busted From d621d4ef959dd5eb8c3de7b7216879d49cf0f224 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sat, 28 Nov 2020 18:29:38 -0500 Subject: [PATCH 033/276] Modify cluster update resources to utilize rolling updates As this is a change that can cause downtime, it is prudent to try to limit the downtime by applying a rolling update methodology. --- .../pgcluster/pgclustercontroller.go | 2 +- internal/operator/cluster/cluster.go | 86 ++++++------------- 2 files changed, 29 insertions(+), 59 deletions(-) diff --git a/internal/controller/pgcluster/pgclustercontroller.go b/internal/controller/pgcluster/pgclustercontroller.go index d8da6658d9..346ea8cb5d 100644 --- a/internal/controller/pgcluster/pgclustercontroller.go +++ b/internal/controller/pgcluster/pgclustercontroller.go @@ -236,7 +236,7 @@ func (c *Controller) onUpdate(oldObj, newObj interface{}) { !reflect.DeepEqual(oldcluster.Spec.Limits, newcluster.Spec.Limits) || !reflect.DeepEqual(oldcluster.Spec.ExporterResources, newcluster.Spec.ExporterResources) || !reflect.DeepEqual(oldcluster.Spec.ExporterLimits, newcluster.Spec.ExporterLimits) { - if err := clusteroperator.UpdateResources(c.Client, c.Client.Config, newcluster); err != nil { + if err := clusteroperator.RollingUpdate(c.Client, c.Client.Config, newcluster, clusteroperator.UpdateResources); err != nil { log.Error(err) return } diff --git a/internal/operator/cluster/cluster.go b/internal/operator/cluster/cluster.go index 651cba0aa6..0994e31c0d 100644 --- a/internal/operator/cluster/cluster.go +++ b/internal/operator/cluster/cluster.go @@ -498,68 +498,38 @@ func UpdateAnnotations(clientset kubernetes.Interface, restConfig *rest.Config, // UpdateResources updates the PostgreSQL instance Deployments to reflect the // update resources (i.e. CPU, memory) -func UpdateResources(clientset kubernetes.Interface, restConfig *rest.Config, cluster *crv1.Pgcluster) error { - ctx := context.TODO() - - // get a list of all of the instance deployments for the cluster - deployments, err := operator.GetInstanceDeployments(clientset, cluster) - - if err != nil { - return err - } - +func UpdateResources(cluster *crv1.Pgcluster, deployment *apps_v1.Deployment) error { // iterate through each PostgreSQL instance deployment and update the // resource values for the database or exporter containers - // - // NOTE: a future version (near future) will first try to detect the primary - // so that all the replicas are updated first, and then the primary gets the - // update - for _, deployment := range deployments.Items { - // now, iterate through each container within that deployment - for index, container := range deployment.Spec.Template.Spec.Containers { - // first check for the database container - if container.Name == "database" { - // first, initialize the requests/limits resource to empty Resource Lists - deployment.Spec.Template.Spec.Containers[index].Resources.Requests = v1.ResourceList{} - deployment.Spec.Template.Spec.Containers[index].Resources.Limits = v1.ResourceList{} - - // now, simply deep copy the values from the CRD - if cluster.Spec.Resources != nil { - deployment.Spec.Template.Spec.Containers[index].Resources.Requests = cluster.Spec.Resources.DeepCopy() - } - - if cluster.Spec.Limits != nil { - deployment.Spec.Template.Spec.Containers[index].Resources.Limits = cluster.Spec.Limits.DeepCopy() - } - // next, check for the exporter container - } else if container.Name == "exporter" { - // first, initialize the requests/limits resource to empty Resource Lists - deployment.Spec.Template.Spec.Containers[index].Resources.Requests = v1.ResourceList{} - deployment.Spec.Template.Spec.Containers[index].Resources.Limits = v1.ResourceList{} - - // now, simply deep copy the values from the CRD - if cluster.Spec.ExporterResources != nil { - deployment.Spec.Template.Spec.Containers[index].Resources.Requests = cluster.Spec.ExporterResources.DeepCopy() - } - - if cluster.Spec.ExporterLimits != nil { - deployment.Spec.Template.Spec.Containers[index].Resources.Limits = cluster.Spec.ExporterLimits.DeepCopy() - } + for index, container := range deployment.Spec.Template.Spec.Containers { + // first check for the database container + if container.Name == "database" { + // first, initialize the requests/limits resource to empty Resource Lists + deployment.Spec.Template.Spec.Containers[index].Resources.Requests = v1.ResourceList{} + deployment.Spec.Template.Spec.Containers[index].Resources.Limits = v1.ResourceList{} + + // now, simply deep copy the values from the CRD + if cluster.Spec.Resources != nil { + deployment.Spec.Template.Spec.Containers[index].Resources.Requests = cluster.Spec.Resources.DeepCopy() + } + if cluster.Spec.Limits != nil { + deployment.Spec.Template.Spec.Containers[index].Resources.Limits = cluster.Spec.Limits.DeepCopy() + } + // next, check for the exporter container + } else if container.Name == "exporter" { + // first, initialize the requests/limits resource to empty Resource Lists + deployment.Spec.Template.Spec.Containers[index].Resources.Requests = v1.ResourceList{} + deployment.Spec.Template.Spec.Containers[index].Resources.Limits = v1.ResourceList{} + + // now, simply deep copy the values from the CRD + if cluster.Spec.ExporterResources != nil { + deployment.Spec.Template.Spec.Containers[index].Resources.Requests = cluster.Spec.ExporterResources.DeepCopy() + } + + if cluster.Spec.ExporterLimits != nil { + deployment.Spec.Template.Spec.Containers[index].Resources.Limits = cluster.Spec.ExporterLimits.DeepCopy() } - } - // Before applying the update, we want to explicitly stop PostgreSQL on each - // instance. This prevents PostgreSQL from having to boot up in crash - // recovery mode. - // - // If an error is returned, we only issue a warning - if err := stopPostgreSQLInstance(clientset, restConfig, deployment); err != nil { - log.Warn(err) - } - // update the deployment with the new values - if _, err := clientset.AppsV1().Deployments(deployment.Namespace). - Update(ctx, &deployment, metav1.UpdateOptions{}); err != nil { - return err } } From 1a84813496a740684b821d28402fce4b96686e25 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 29 Nov 2020 14:51:22 -0500 Subject: [PATCH 034/276] Modify cluster update annotations to use rolling updates Modifying the annotations on the template portion of a Deployment Spec causes each Pod that is under management of a Deployment to be restarted. For managing a database server, this can be less than ideal. As such, it is prudent to employ a rolling update strategy for annotation updates on database instances to minimize downtime on the primary instance. --- .../pgcluster/pgclustercontroller.go | 12 ++--- internal/operator/cluster/cluster.go | 46 ++++++------------- 2 files changed, 20 insertions(+), 38 deletions(-) diff --git a/internal/controller/pgcluster/pgclustercontroller.go b/internal/controller/pgcluster/pgclustercontroller.go index 346ea8cb5d..9966ea6a66 100644 --- a/internal/controller/pgcluster/pgclustercontroller.go +++ b/internal/controller/pgcluster/pgclustercontroller.go @@ -371,12 +371,6 @@ func updateAnnotations(c *Controller, oldCluster *crv1.Pgcluster, newCluster *cr // so if there are changes, we can apply them to the various deployments, // but only do so if we have to - if len(annotationsPostgres) != 0 { - if err := clusteroperator.UpdateAnnotations(c.Client, c.Client.Config, newCluster, annotationsPostgres); err != nil { - return err - } - } - if len(annotationsBackrest) != 0 { if err := backrestoperator.UpdateAnnotations(c.Client, newCluster, annotationsBackrest); err != nil { return err @@ -389,6 +383,12 @@ func updateAnnotations(c *Controller, oldCluster *crv1.Pgcluster, newCluster *cr } } + if len(annotationsPostgres) != 0 { + if err := clusteroperator.RollingUpdate(c.Client, c.Client.Config, newCluster, clusteroperator.UpdateAnnotations); err != nil { + return err + } + } + return nil } diff --git a/internal/operator/cluster/cluster.go b/internal/operator/cluster/cluster.go index 0994e31c0d..95c2e7dca7 100644 --- a/internal/operator/cluster/cluster.go +++ b/internal/operator/cluster/cluster.go @@ -456,44 +456,26 @@ func ScaleDownBase(clientset kubeapi.Interface, replica *crv1.Pgreplica, namespa // UpdateAnnotations updates the annotations in the "template" portion of a // PostgreSQL deployment -func UpdateAnnotations(clientset kubernetes.Interface, restConfig *rest.Config, - cluster *crv1.Pgcluster, annotations map[string]string) error { - ctx := context.TODO() - var updateError error +func UpdateAnnotations(cluster *crv1.Pgcluster, deployment *apps_v1.Deployment) error { + log.Debugf("update annotations on [%s]", deployment.Name) + annotations := map[string]string{} - // first, get a list of all of the instance deployments for the cluster - deployments, err := operator.GetInstanceDeployments(clientset, cluster) + // store the global annotations first + for k, v := range cluster.Spec.Annotations.Global { + annotations[k] = v + } - if err != nil { - return err + // then store the postgres specific annotations + for k, v := range cluster.Spec.Annotations.Postgres { + annotations[k] = v } - // now update each deployment with the new annotations - for _, deployment := range deployments.Items { - log.Debugf("update annotations on [%s]", deployment.Name) - log.Debugf("new annotations: %v", annotations) + log.Debugf("new annotations: %v", annotations) - deployment.Spec.Template.ObjectMeta.SetAnnotations(annotations) + // set the annotations on the deployment object + deployment.Spec.Template.ObjectMeta.SetAnnotations(annotations) - // Before applying the update, we want to explicitly stop PostgreSQL on each - // instance. This prevents PostgreSQL from having to boot up in crash - // recovery mode. - // - // If an error is returned, we only issue a warning - if err := stopPostgreSQLInstance(clientset, restConfig, deployment); err != nil { - log.Warn(err) - } - - // finally, update the Deployment. If something errors, we'll log that there - // was an error, but continue with processing the other deployments - if _, err := clientset.AppsV1().Deployments(deployment.Namespace). - Update(ctx, &deployment, metav1.UpdateOptions{}); err != nil { - log.Error(err) - updateError = err - } - } - - return updateError + return nil } // UpdateResources updates the PostgreSQL instance Deployments to reflect the From b5a84cfa16c6eaa42484757f1b0f01043c951f68 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Fri, 4 Dec 2020 15:54:50 -0500 Subject: [PATCH 035/276] Modify adding tablespaces to use rolling updates The `pgo update cluser --tablespace` functionality now leverages the rolling update algorithm to minimize the appearance of downtime to any connected clients. --- .../pgcluster/pgclustercontroller.go | 47 ++++-- internal/operator/cluster/cluster.go | 157 +++++++----------- 2 files changed, 90 insertions(+), 114 deletions(-) diff --git a/internal/controller/pgcluster/pgclustercontroller.go b/internal/controller/pgcluster/pgclustercontroller.go index 9966ea6a66..d363445106 100644 --- a/internal/controller/pgcluster/pgclustercontroller.go +++ b/internal/controller/pgcluster/pgclustercontroller.go @@ -25,8 +25,10 @@ import ( "github.com/crunchydata/postgres-operator/internal/config" "github.com/crunchydata/postgres-operator/internal/kubeapi" + "github.com/crunchydata/postgres-operator/internal/operator" backrestoperator "github.com/crunchydata/postgres-operator/internal/operator/backrest" clusteroperator "github.com/crunchydata/postgres-operator/internal/operator/cluster" + "github.com/crunchydata/postgres-operator/internal/operator/pvc" "github.com/crunchydata/postgres-operator/internal/util" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" informers "github.com/crunchydata/postgres-operator/pkg/generated/informers/externalversions/crunchydata.com/v1" @@ -420,31 +422,46 @@ func updatePgBouncer(c *Controller, oldCluster *crv1.Pgcluster, newCluster *crv1 // updateTablespaces updates the PostgreSQL instance Deployments to reflect the // new PostgreSQL tablespaces that should be added func updateTablespaces(c *Controller, oldCluster *crv1.Pgcluster, newCluster *crv1.Pgcluster) error { - // to help the Operator function do less work, we will get a list of new - // tablespaces. Though these are already present in the CRD, this will isolate - // exactly which PVCs need to be created - // - // To do this, iterate through the the tablespace mount map that is present in - // the new cluster. - newTablespaces := map[string]crv1.PgStorageSpec{} + // first, get a list of all of the instance deployments for the cluster + deployments, err := operator.GetInstanceDeployments(c.Client, newCluster) + if err != nil { + return err + } + + // iterate through the the tablespace mount map that is present in and create + // any new PVCs for tablespaceName, storageSpec := range newCluster.Spec.TablespaceMounts { // if the tablespace does not exist in the old version of the cluster, // then add it in! - if _, ok := oldCluster.Spec.TablespaceMounts[tablespaceName]; !ok { - log.Debugf("new tablespace found: [%s]", tablespaceName) + if _, ok := oldCluster.Spec.TablespaceMounts[tablespaceName]; ok { + continue + } + + log.Debugf("new tablespace found: [%s]", tablespaceName) + + // This is a new tablespace, great. Create the new PVCs. + // The PVCs are created for each **instance** in the cluster, as every + // instance needs to have a distinct PVC for each tablespace + // get the name of the tablespace PVC for that instance. + for _, deployment := range deployments.Items { + tablespacePVCName := operator.GetTablespacePVCName(deployment.Name, tablespaceName) - newTablespaces[tablespaceName] = storageSpec + log.Debugf("creating tablespace PVC [%s] for [%s]", tablespacePVCName, deployment.Name) + + // Now create it! If it errors, we just need to return, which + // potentially leaves things in an inconsistent state, but at this point + // only PVC objects have been created + if _, err := pvc.CreateIfNotExists(c.Client, storageSpec, tablespacePVCName, + newCluster.Name, newCluster.Namespace); err != nil { + return err + } } } // alright, update the tablespace entries for this cluster! // if it returns an error, pass the error back up to the caller - if err := clusteroperator.UpdateTablespaces(c.Client, c.Client.Config, newCluster, newTablespaces); err != nil { - return err - } - - return nil + return clusteroperator.RollingUpdate(c.Client, c.Client.Config, newCluster, clusteroperator.UpdateTablespaces) } // WorkerCount returns the worker count for the controller diff --git a/internal/operator/cluster/cluster.go b/internal/operator/cluster/cluster.go index 95c2e7dca7..76eb3834c1 100644 --- a/internal/operator/cluster/cluster.go +++ b/internal/operator/cluster/cluster.go @@ -520,118 +520,77 @@ func UpdateResources(cluster *crv1.Pgcluster, deployment *apps_v1.Deployment) er // UpdateTablespaces updates the PostgreSQL instance Deployments to update // what tablespaces are mounted. -// Though any new tablespaces are present in the CRD, to attempt to do less work -// this function takes a map of the new tablespaces that are being added, so we -// only have to check and create the PVCs that are being mounted at this time -// -// To do this, iterate through the tablespace mount map that is present in the -// new cluster. -func UpdateTablespaces(clientset kubernetes.Interface, restConfig *rest.Config, - cluster *crv1.Pgcluster, newTablespaces map[string]crv1.PgStorageSpec) error { - ctx := context.TODO() - - // first, get a list of all of the instance deployments for the cluster - deployments, err := operator.GetInstanceDeployments(clientset, cluster) - - if err != nil { - return err - } - - tablespaceVolumes := make([]map[string]operator.StorageResult, len(deployments.Items)) - - // now we can start creating the new tablespaces! First, create the new - // PVCs. The PVCs are created for each **instance** in the cluster, as every - // instance needs to have a distinct PVC for each tablespace - for i, deployment := range deployments.Items { - tablespaceVolumes[i] = make(map[string]operator.StorageResult) - - for tablespaceName, storageSpec := range newTablespaces { - // get the name of the tablespace PVC for that instance - tablespacePVCName := operator.GetTablespacePVCName(deployment.Name, tablespaceName) - - log.Debugf("creating tablespace PVC [%s] for [%s]", tablespacePVCName, deployment.Name) - - // and now create it! If it errors, we just need to return, which - // potentially leaves things in an inconsistent state, but at this point - // only PVC objects have been created - tablespaceVolumes[i][tablespaceName], err = pvc.CreateIfNotExists(clientset, - storageSpec, tablespacePVCName, cluster.Name, cluster.Namespace) - if err != nil { - return err +func UpdateTablespaces(cluster *crv1.Pgcluster, deployment *apps_v1.Deployment) error { + // update the volume portion of the Deployment spec to reflect all of the + // available tablespaces + for tablespaceName, storageSpec := range cluster.Spec.TablespaceMounts { + // go through the volume list and see if there is already a volume for this + // if there is, skip + found := false + volumeName := operator.GetTablespaceVolumeName(tablespaceName) + + for _, volume := range deployment.Spec.Template.Spec.Volumes { + if volume.Name == volumeName { + found = true + break } } - } - // now the fun step: update each deployment with the new volumes - for i, deployment := range deployments.Items { - log.Debugf("attach tablespace volumes to [%s]", deployment.Name) - - // iterate through each table space and prepare the Volume and - // VolumeMount clause for each instance - for tablespaceName := range newTablespaces { - // this is the volume to be added for the tablespace - volume := v1.Volume{ - Name: operator.GetTablespaceVolumeName(tablespaceName), - VolumeSource: tablespaceVolumes[i][tablespaceName].VolumeSource(), - } - - // add the volume to the list of volumes - deployment.Spec.Template.Spec.Volumes = append(deployment.Spec.Template.Spec.Volumes, volume) - - // now add the volume mount point to that of the database container - volumeMount := v1.VolumeMount{ - MountPath: fmt.Sprintf("%s%s", config.VOLUME_TABLESPACE_PATH_PREFIX, tablespaceName), - Name: operator.GetTablespaceVolumeName(tablespaceName), - } - - // we can do this as we always know that the "database" container is the - // first container in the list - deployment.Spec.Template.Spec.Containers[0].VolumeMounts = append( - deployment.Spec.Template.Spec.Containers[0].VolumeMounts, volumeMount) + if found { + continue + } - // add any supplemental groups specified in storage configuration. - // SecurityContext is always initialized because we use fsGroup. - deployment.Spec.Template.Spec.SecurityContext.SupplementalGroups = append( - deployment.Spec.Template.Spec.SecurityContext.SupplementalGroups, - tablespaceVolumes[i][tablespaceName].SupplementalGroups...) + // create the volume definition for the tablespace + storageResult := operator.StorageResult{ + PersistentVolumeClaimName: operator.GetTablespacePVCName(deployment.Name, tablespaceName), + SupplementalGroups: storageSpec.GetSupplementalGroups(), } - // find the "PGHA_TABLESPACES" value and update it with the new tablespace - // name list - ok := false - for i, envVar := range deployment.Spec.Template.Spec.Containers[0].Env { - // yup, it's an old fashioned linear time lookup - if envVar.Name == "PGHA_TABLESPACES" { - deployment.Spec.Template.Spec.Containers[0].Env[i].Value = operator.GetTablespaceNames( - cluster.Spec.TablespaceMounts) - ok = true - } + volume := v1.Volume{ + Name: volumeName, + VolumeSource: storageResult.VolumeSource(), } - // if its not found, we need to add it to the env - if !ok { - envVar := v1.EnvVar{ - Name: "PGHA_TABLESPACES", - Value: operator.GetTablespaceNames(cluster.Spec.TablespaceMounts), - } - deployment.Spec.Template.Spec.Containers[0].Env = append(deployment.Spec.Template.Spec.Containers[0].Env, envVar) + // add the volume to the list of volumes + deployment.Spec.Template.Spec.Volumes = append(deployment.Spec.Template.Spec.Volumes, volume) + + // now add the volume mount point to that of the database container + volumeMount := v1.VolumeMount{ + MountPath: fmt.Sprintf("%s%s", config.VOLUME_TABLESPACE_PATH_PREFIX, tablespaceName), + Name: volumeName, } - // Before applying the update, we want to explicitly stop PostgreSQL on each - // instance. This prevents PostgreSQL from having to boot up in crash - // recovery mode. - // - // If an error is returned, we only issue a warning - if err := stopPostgreSQLInstance(clientset, restConfig, deployment); err != nil { - log.Warn(err) + // we can do this as we always know that the "database" container is the + // first container in the list + deployment.Spec.Template.Spec.Containers[0].VolumeMounts = append( + deployment.Spec.Template.Spec.Containers[0].VolumeMounts, volumeMount) + + // add any supplemental groups specified in storage configuration. + // SecurityContext is always initialized because we use fsGroup. + deployment.Spec.Template.Spec.SecurityContext.SupplementalGroups = append( + deployment.Spec.Template.Spec.SecurityContext.SupplementalGroups, + storageResult.SupplementalGroups...) + } + + // find the "PGHA_TABLESPACES" value and update it with the new tablespace + // name list + ok := false + for i, envVar := range deployment.Spec.Template.Spec.Containers[0].Env { + // yup, it's an old fashioned linear time lookup + if envVar.Name == "PGHA_TABLESPACES" { + deployment.Spec.Template.Spec.Containers[0].Env[i].Value = operator.GetTablespaceNames( + cluster.Spec.TablespaceMounts) + ok = true } + } - // finally, update the Deployment. Potential to put things into an - // inconsistent state if any of these updates fail - if _, err := clientset.AppsV1().Deployments(deployment.Namespace). - Update(ctx, &deployment, metav1.UpdateOptions{}); err != nil { - return err + // if its not found, we need to add it to the env + if !ok { + envVar := v1.EnvVar{ + Name: "PGHA_TABLESPACES", + Value: operator.GetTablespaceNames(cluster.Spec.TablespaceMounts), } + deployment.Spec.Template.Spec.Containers[0].Env = append(deployment.Spec.Template.Spec.Containers[0].Env, envVar) } return nil From d9959a5b968887c16abf89d7707a7922d82f14ab Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sat, 5 Dec 2020 16:07:26 -0500 Subject: [PATCH 036/276] Add `--rolling` flag to `pgo restart` The `--rolling` flag allows for one to specify a restart on a PosgreSQL cluster to occur in a rolling fashion, i.e. all the replicas a restarted, then a switchover occurs, then the newly demoted primary is restarted. This subsequently creates a task custom resource to perform the rolling update, as said updates can take some time to process. Issue: [ch9881] --- cmd/pgo/cmd/restart.go | 12 +++ .../pgo-client/reference/pgo_restart.md | 8 +- .../apiserver/restartservice/restartimpl.go | 42 +++++++++ internal/config/labels.go | 1 + .../controller/pgtask/pgtaskcontroller.go | 16 ++++ pkg/apis/crunchydata.com/v1/task.go | 85 +++++++++++-------- pkg/apiservermsgs/restartmsgs.go | 1 + 7 files changed, 128 insertions(+), 37 deletions(-) diff --git a/cmd/pgo/cmd/restart.go b/cmd/pgo/cmd/restart.go index f784d82004..02d80a1229 100644 --- a/cmd/pgo/cmd/restart.go +++ b/cmd/pgo/cmd/restart.go @@ -28,6 +28,8 @@ import ( "github.com/spf13/cobra" ) +var RollingUpdate bool + var restartCmd = &cobra.Command{ Use: "restart", Short: "Restarts the PostgrSQL database within a PostgreSQL cluster", @@ -36,6 +38,9 @@ var restartCmd = &cobra.Command{ For example, to restart the primary and all replicas: pgo restart mycluster + To restart the primary and all replicas using a rolling update strategy: + pgo restart mycluster --rolling + Or target a specific instance within the cluster: pgo restart mycluster --target=mycluster-abcd @@ -78,6 +83,7 @@ func init() { restartCmd.Flags().BoolVar(&NoPrompt, "no-prompt", false, "No command line confirmation.") restartCmd.Flags().StringVarP(&OutputFormat, "output", "o", "", `The output format. Supported types are: "json"`) restartCmd.Flags().BoolVarP(&Query, "query", "", false, "Prints the list of instances that can be restarted.") + restartCmd.Flags().BoolVar(&RollingUpdate, "rolling", false, "Performs a rolling restart. Cannot be used with other flags.") restartCmd.Flags().StringArrayVarP(&Targets, "target", "", []string{}, "The instance that will be restarted.") } @@ -91,6 +97,12 @@ func restart(clusterName, namespace string) { request.ClusterName = clusterName request.Targets = Targets request.ClientVersion = msgs.PGO_VERSION + request.RollingUpdate = RollingUpdate + + if request.RollingUpdate && len(request.Targets) > 0 { + fmt.Println("Error: cannot use --rolling with other flags") + os.Exit(1) + } response, err := api.Restart(httpclient, &SessionCredentials, request) if err != nil { diff --git a/docs/content/pgo-client/reference/pgo_restart.md b/docs/content/pgo-client/reference/pgo_restart.md index dc0517f1db..2a56f8ed12 100644 --- a/docs/content/pgo-client/reference/pgo_restart.md +++ b/docs/content/pgo-client/reference/pgo_restart.md @@ -12,6 +12,9 @@ Restarts one or more PostgreSQL databases within a PostgreSQL cluster. For example, to restart the primary and all replicas: pgo restart mycluster + To restart the primary and all replicas using a rolling update strategy: + pgo restart mycluster --rolling + Or target a specific instance within the cluster: pgo restart mycluster --target=mycluster-abcd @@ -29,13 +32,14 @@ pgo restart [flags] --no-prompt No command line confirmation. -o, --output string The output format. Supported types are: "json" --query Prints the list of instances that can be restarted. + --rolling Performs a rolling restart. Cannot be used with other flags. --target stringArray The instance that will be restarted. ``` ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -49,4 +53,4 @@ pgo restart [flags] * [pgo](/pgo-client/reference/pgo/) - The pgo command line interface. -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 5-Dec-2020 diff --git a/internal/apiserver/restartservice/restartimpl.go b/internal/apiserver/restartservice/restartimpl.go index 5d1545d8e4..dc1b4f95ce 100644 --- a/internal/apiserver/restartservice/restartimpl.go +++ b/internal/apiserver/restartservice/restartimpl.go @@ -23,8 +23,10 @@ import ( "github.com/crunchydata/postgres-operator/internal/config" "github.com/crunchydata/postgres-operator/internal/patroni" "github.com/crunchydata/postgres-operator/internal/util" + crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" + kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -63,6 +65,46 @@ func Restart(request *msgs.RestartRequest, pgouser string) msgs.RestartResponse return resp } + // if a rolling update is requested, this takes a detour to create a pgtask + // to accomplish this + if request.RollingUpdate { + // since a rolling update takes time, this needs to be performed as a + // separate task + // Create a pgtask + task := &crv1.Pgtask{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-%s", cluster.Name, config.LABEL_RESTART), + Namespace: cluster.Namespace, + Labels: map[string]string{ + config.LABEL_PG_CLUSTER: cluster.Name, + config.LABEL_PGOUSER: pgouser, + }, + }, + Spec: crv1.PgtaskSpec{ + TaskType: crv1.PgtaskRollingUpdate, + Parameters: map[string]string{ + config.LABEL_PG_CLUSTER: cluster.Name, + }, + }, + } + + // remove any previous rolling restart, then add a new one + if err := apiserver.Clientset.CrunchydataV1().Pgtasks(task.Namespace).Delete(ctx, task.Name, + metav1.DeleteOptions{}); err != nil && !kerrors.IsNotFound(err) { + resp.Status.Code = msgs.Error + resp.Status.Msg = err.Error() + return resp + } + + if _, err := apiserver.Clientset.CrunchydataV1().Pgtasks(cluster.Namespace).Create(ctx, task, + metav1.CreateOptions{}); err != nil { + resp.Status.Code = msgs.Error + resp.Status.Msg = err.Error() + } + + return resp + } + var restartResults []patroni.RestartResult // restart either the whole cluster, or just any targets specified patroniClient := patroni.NewPatroniClient(apiserver.RESTConfig, apiserver.Clientset, diff --git a/internal/config/labels.go b/internal/config/labels.go index 6a24b72494..eb5522b092 100644 --- a/internal/config/labels.go +++ b/internal/config/labels.go @@ -27,6 +27,7 @@ const LABEL_PGTASK = "pg-task" const LABEL_AUTOFAIL = "autofail" const LABEL_FAILOVER = "failover" +const LABEL_RESTART = "restart" const LABEL_TARGET = "target" const LABEL_RMDATA = "pgrmdata" diff --git a/internal/controller/pgtask/pgtaskcontroller.go b/internal/controller/pgtask/pgtaskcontroller.go index 3d3706d5fa..0a60af9eb6 100644 --- a/internal/controller/pgtask/pgtaskcontroller.go +++ b/internal/controller/pgtask/pgtaskcontroller.go @@ -29,7 +29,9 @@ import ( crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" pgo "github.com/crunchydata/postgres-operator/pkg/generated/clientset/versioned" informers "github.com/crunchydata/postgres-operator/pkg/generated/informers/externalversions/crunchydata.com/v1" + log "github.com/sirupsen/logrus" + appsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/cache" @@ -128,6 +130,20 @@ func (c *Controller) processNextItem() bool { } else { log.Debugf("skipping duplicate onAdd failover task %s/%s", keyNamespace, keyResourceName) } + case crv1.PgtaskRollingUpdate: + log.Debug("rolling update task added") + // first, attempt to get the pgcluster object + clusterName := tmpTask.Spec.Parameters[config.LABEL_PG_CLUSTER] + + if cluster, err := c.Client.CrunchydataV1().Pgclusters(tmpTask.Namespace). + Get(ctx, clusterName, metav1.GetOptions{}); err == nil { + if err := clusteroperator.RollingUpdate(c.Client, c.Client.Config, cluster, + func(*crv1.Pgcluster, *appsv1.Deployment) error { return nil }); err != nil { + log.Errorf("rolling update failed: %q", err.Error()) + } + } else { + log.Debugf("rolling update failed: could not find cluster %q", clusterName) + } case crv1.PgtaskDeleteData: log.Debug("delete data task added") diff --git a/pkg/apis/crunchydata.com/v1/task.go b/pkg/apis/crunchydata.com/v1/task.go index 1475b61ad7..7b79896e67 100644 --- a/pkg/apis/crunchydata.com/v1/task.go +++ b/pkg/apis/crunchydata.com/v1/task.go @@ -22,41 +22,56 @@ import ( // PgtaskResourcePlural ... const PgtaskResourcePlural = "pgtasks" -const PgtaskDeleteBackups = "delete-backups" -const PgtaskDeleteData = "delete-data" -const PgtaskFailover = "failover" -const PgtaskAutoFailover = "autofailover" -const PgtaskAddPolicies = "addpolicies" - -const PgtaskUpgrade = "clusterupgrade" -const PgtaskUpgradeCreated = "cluster upgrade - task created" -const PgtaskUpgradeInProgress = "cluster upgrade - in progress" - -const PgtaskPgAdminAdd = "add-pgadmin" -const PgtaskPgAdminDelete = "delete-pgadmin" - -const PgtaskWorkflow = "workflow" -const PgtaskWorkflowCreateClusterType = "createcluster" -const PgtaskWorkflowBackrestRestoreType = "pgbackrestrestore" -const PgtaskWorkflowBackupType = "backupworkflow" -const PgtaskWorkflowSubmittedStatus = "task submitted" -const PgtaskWorkflowCompletedStatus = "task completed" -const PgtaskWorkflowID = "workflowid" - -const PgtaskWorkflowBackrestRestorePVCCreatedStatus = "restored PVC created" -const PgtaskWorkflowBackrestRestorePrimaryCreatedStatus = "restored Primary created" -const PgtaskWorkflowBackrestRestoreJobCreatedStatus = "restore job created" - -const PgtaskBackrest = "backrest" -const PgtaskBackrestBackup = "backup" -const PgtaskBackrestInfo = "info" -const PgtaskBackrestRestore = "restore" -const PgtaskBackrestStanzaCreate = "stanza-create" - -const PgtaskpgDump = "pgdump" -const PgtaskpgDumpBackup = "pgdumpbackup" -const PgtaskpgDumpInfo = "pgdumpinfo" -const PgtaskpgRestore = "pgrestore" +const ( + PgtaskDeleteBackups = "delete-backups" + PgtaskDeleteData = "delete-data" + PgtaskFailover = "failover" + PgtaskAutoFailover = "autofailover" + PgtaskAddPolicies = "addpolicies" + PgtaskRollingUpdate = "rolling update" +) + +const ( + PgtaskUpgrade = "clusterupgrade" + PgtaskUpgradeCreated = "cluster upgrade - task created" + PgtaskUpgradeInProgress = "cluster upgrade - in progress" +) + +const ( + PgtaskPgAdminAdd = "add-pgadmin" + PgtaskPgAdminDelete = "delete-pgadmin" +) + +const ( + PgtaskWorkflow = "workflow" + PgtaskWorkflowCreateClusterType = "createcluster" + PgtaskWorkflowBackrestRestoreType = "pgbackrestrestore" + PgtaskWorkflowBackupType = "backupworkflow" + PgtaskWorkflowSubmittedStatus = "task submitted" + PgtaskWorkflowCompletedStatus = "task completed" + PgtaskWorkflowID = "workflowid" +) + +const ( + PgtaskWorkflowBackrestRestorePVCCreatedStatus = "restored PVC created" + PgtaskWorkflowBackrestRestorePrimaryCreatedStatus = "restored Primary created" + PgtaskWorkflowBackrestRestoreJobCreatedStatus = "restore job created" +) + +const ( + PgtaskBackrest = "backrest" + PgtaskBackrestBackup = "backup" + PgtaskBackrestInfo = "info" + PgtaskBackrestRestore = "restore" + PgtaskBackrestStanzaCreate = "stanza-create" +) + +const ( + PgtaskpgDump = "pgdump" + PgtaskpgDumpBackup = "pgdumpbackup" + PgtaskpgDumpInfo = "pgdumpinfo" + PgtaskpgRestore = "pgrestore" +) // this is ported over from legacy backup code const PgBackupJobSubmitted = "Backup Job Submitted" diff --git a/pkg/apiservermsgs/restartmsgs.go b/pkg/apiservermsgs/restartmsgs.go index c0b32d3d00..a36307739c 100644 --- a/pkg/apiservermsgs/restartmsgs.go +++ b/pkg/apiservermsgs/restartmsgs.go @@ -47,6 +47,7 @@ type InstanceDetail struct { type RestartRequest struct { Namespace string ClusterName string + RollingUpdate bool Targets []string ClientVersion string } From 1aefe228cd45b0af3f5f51e644e77b898c6b742c Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 8 Dec 2020 22:19:40 -0500 Subject: [PATCH 037/276] Do not consider bootstrap Pod in `pgo df` The bootstrap Pod, a remnaint of a cluster restore, gets caught up in the `pgo df` search, but unfortunately this is not a valid Pod. This exlcude this Pod from being considered. Issue: [ch2029] Issue: #2029 --- internal/apiserver/dfservice/dfimpl.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/apiserver/dfservice/dfimpl.go b/internal/apiserver/dfservice/dfimpl.go index 6c11bd9d36..0b2ac196af 100644 --- a/internal/apiserver/dfservice/dfimpl.go +++ b/internal/apiserver/dfservice/dfimpl.go @@ -147,7 +147,8 @@ func getClusterDf(cluster *crv1.Pgcluster, clusterResultsChannel chan msgs.DfDet ctx := context.TODO() log.Debugf("pod df: %s", cluster.Spec.Name) - selector := fmt.Sprintf("%s=%s", config.LABEL_PG_CLUSTER, cluster.Spec.Name) + selector := fmt.Sprintf("%s=%s,!%s", + config.LABEL_PG_CLUSTER, cluster.Spec.Name, config.LABEL_PGHA_BOOTSTRAP) pods, err := apiserver.Clientset.CoreV1().Pods(cluster.Spec.Namespace).List(ctx, metav1.ListOptions{LabelSelector: selector}) From 23c935d7a7f96d57c0157624a0bc04396185e3cb Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 8 Dec 2020 22:41:06 -0500 Subject: [PATCH 038/276] Delete bootstrap Job once it successfully completes When the bootstrap Job completes successfully after a restore, it contains information that ends up being consumed by other parts of the Operator system, such as Patroni. As the logs from the Job do not provide much, if any, helpful information after a restore succeeds, it's best to have the Operator eliminate the job. As such, this changes the behavior so that the bootstrap Job is removed. As this has lead to some buggy behavior, this is being considered as a bug fix, as regular operational work would dictate that the Job is removed anyway. Issue: [ch9919] --- internal/controller/job/bootstraphandler.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/controller/job/bootstraphandler.go b/internal/controller/job/bootstraphandler.go index 4c4f383d25..580da57041 100644 --- a/internal/controller/job/bootstraphandler.go +++ b/internal/controller/job/bootstraphandler.go @@ -82,7 +82,7 @@ func (c *Controller) handleBootstrapUpdate(job *apiv1.Job) error { // If the job was successful we updated the state of the pgcluster to a "bootstrapped" status. // This will then trigger full initialization of the cluster. We also cleanup any resources - // from the bootstrap job. + // from the bootstrap job and delete the job itself if cluster.Status.State == crv1.PgclusterStateBootstrapping { if err := c.cleanupBootstrapResources(job, cluster, restore); err != nil { @@ -103,6 +103,11 @@ func (c *Controller) handleBootstrapUpdate(job *apiv1.Job) error { log.Error(err) return err } + + // as it is no longer needed, delete the job + deletePropagation := metav1.DeletePropagationBackground + return c.Client.BatchV1().Jobs(namespace).Delete(ctx, job.Name, + metav1.DeleteOptions{PropagationPolicy: &deletePropagation}) } if restore { From 57c9815ab3ad14575725aa15daab286407371dc7 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Thu, 10 Dec 2020 12:38:36 -0500 Subject: [PATCH 039/276] Only consider running Pods for `pgo test` By adding this limitation, Pods such as Evicted Pods would not be considered as a part of `pgo test` output, as this could present some odd scenarios, such as the presence of two primaries. Issue: [ch9931] Issue: #2095 --- .../apiserver/clusterservice/clusterimpl.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index 90a062e40d..5c51d98514 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -38,6 +38,7 @@ import ( kerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/util/validation" "k8s.io/client-go/kubernetes" ) @@ -2065,10 +2066,16 @@ func GetPrimaryAndReplicaPods(cluster *crv1.Pgcluster, ns string) ([]msgs.ShowCl ctx := context.TODO() output := make([]msgs.ShowClusterPod, 0) + // find all of the Pods that represent Postgres primary and replicas. + // only consider running Pods selector := config.LABEL_SERVICE_NAME + "=" + cluster.Spec.Name + "," + config.LABEL_DEPLOYMENT_NAME - log.Debugf("selector for GetPrimaryAndReplicaPods is %s", selector) - pods, err := apiserver.Clientset.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{LabelSelector: selector}) + options := metav1.ListOptions{ + FieldSelector: fields.OneTermEqualSelector("status.phase", string(v1.PodRunning)).String(), + LabelSelector: selector, + } + + pods, err := apiserver.Clientset.CoreV1().Pods(ns).List(ctx, options) if err != nil { return output, err } @@ -2088,9 +2095,12 @@ func GetPrimaryAndReplicaPods(cluster *crv1.Pgcluster, ns string) ([]msgs.ShowCl } selector = config.LABEL_SERVICE_NAME + "=" + cluster.Spec.Name + "-replica" + "," + config.LABEL_DEPLOYMENT_NAME - log.Debugf("selector for GetPrimaryAndReplicaPods is %s", selector) + options = metav1.ListOptions{ + FieldSelector: fields.OneTermEqualSelector("status.phase", string(v1.PodRunning)).String(), + LabelSelector: selector, + } - pods, err = apiserver.Clientset.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{LabelSelector: selector}) + pods, err = apiserver.Clientset.CoreV1().Pods(ns).List(ctx, options) if err != nil { return output, err } From 1b5fe49ce9f2993c30985acc75aed34bab25b391 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Thu, 10 Dec 2020 17:06:21 -0500 Subject: [PATCH 040/276] Use poll utility provided by Kubernetes for waiting This moves several home-constructed methods to using a similarly constructed one that is maintained upstream. Provides more consistency across the code that can serve future implementation. --- internal/controller/pod/promotionhandler.go | 56 ++++++++++----------- internal/operator/backrest/backup.go | 48 +++++++++--------- internal/operator/cluster/clusterlogic.go | 46 +++++++---------- internal/operator/cluster/rolling.go | 38 +++++++------- internal/operator/cluster/upgrade.go | 33 +++++------- 5 files changed, 100 insertions(+), 121 deletions(-) diff --git a/internal/controller/pod/promotionhandler.go b/internal/controller/pod/promotionhandler.go index 2dbc34ab6f..123f422d46 100644 --- a/internal/controller/pod/promotionhandler.go +++ b/internal/controller/pod/promotionhandler.go @@ -31,6 +31,7 @@ import ( log "github.com/sirupsen/logrus" apiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) @@ -132,39 +133,36 @@ func waitForStandbyPromotion(restConfig *rest.Config, clientset kubernetes.Inter // wait for the server to accept writes to ensure standby has truly been disabled before // proceeding - duration := time.After(isStandbyDisabledTimeout) - tick := time.NewTicker(isStandbyDisabledTick) - defer tick.Stop() - for { - select { - case <-duration: - return fmt.Errorf("timed out waiting for cluster %s to accept writes after disabling "+ - "standby mode", cluster.Name) - case <-tick.C: + if err := wait.Poll(isStandbyDisabledTick, isStandbyDisabledTimeout, func() (bool, error) { + if !recoveryDisabled { + cmd := isInRecoveryCMD + cmd = append(cmd, cluster.Spec.Port) + + isInRecoveryStr, _, _ := kubeapi.ExecToPodThroughAPI(restConfig, clientset, + cmd, "database", newPod.Name, newPod.Namespace, nil) + + recoveryDisabled = strings.Contains(isInRecoveryStr, "f") + if !recoveryDisabled { - cmd := isInRecoveryCMD - cmd = append(cmd, cluster.Spec.Port) - - isInRecoveryStr, _, _ := kubeapi.ExecToPodThroughAPI(restConfig, clientset, - cmd, newPod.Spec.Containers[0].Name, newPod.Name, - newPod.Namespace, nil) - if strings.Contains(isInRecoveryStr, "f") { - recoveryDisabled = true - } - } - if recoveryDisabled { - primaryJSONStr, _, _ := kubeapi.ExecToPodThroughAPI(restConfig, clientset, - leaderStatusCMD, newPod.Spec.Containers[0].Name, newPod.Name, - newPod.Namespace, nil) - var primaryJSON map[string]interface{} - json.Unmarshal([]byte(primaryJSONStr), &primaryJSON) - if primaryJSON["state"] == "running" && (primaryJSON["pending_restart"] == nil || - !primaryJSON["pending_restart"].(bool)) { - return nil - } + return false, nil } } + + primaryJSONStr, _, _ := kubeapi.ExecToPodThroughAPI(restConfig, clientset, + leaderStatusCMD, newPod.Spec.Containers[0].Name, newPod.Name, + newPod.Namespace, nil) + + primaryJSON := map[string]interface{}{} + _ = json.Unmarshal([]byte(primaryJSONStr), &primaryJSON) + + return (primaryJSON["state"] == "running" && (primaryJSON["pending_restart"] == nil || + !primaryJSON["pending_restart"].(bool))), nil + }); err != nil { + return fmt.Errorf("timed out waiting for cluster %s to accept writes after disabling "+ + "standby mode", cluster.Name) } + + return nil } // cleanAndCreatePostFailoverBackup cleans up any existing backup resources and then creates diff --git a/internal/operator/backrest/backup.go b/internal/operator/backrest/backup.go index 89f4b8a29f..8d3cdeba4a 100644 --- a/internal/operator/backrest/backup.go +++ b/internal/operator/backrest/backup.go @@ -19,6 +19,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "os" "regexp" @@ -37,6 +38,7 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes" ) @@ -61,14 +63,16 @@ type backrestJobTemplateFields struct { PgbackrestRestoreVolumeMounts string } -var backrestPgHostRegex = regexp.MustCompile("--db-host|--pg1-host") -var backrestPgPathRegex = regexp.MustCompile("--db-path|--pg1-path") +var ( + backrestPgHostRegex = regexp.MustCompile("--db-host|--pg1-host") + backrestPgPathRegex = regexp.MustCompile("--db-path|--pg1-path") +) // Backrest ... func Backrest(namespace string, clientset kubernetes.Interface, task *crv1.Pgtask) { ctx := context.TODO() - //create the Job to run the backrest command + // create the Job to run the backrest command cmd := task.Spec.Parameters[config.LABEL_BACKREST_COMMAND] @@ -129,7 +133,7 @@ func Backrest(namespace string, clientset kubernetes.Interface, task *crv1.Pgtas } clientset.BatchV1().Jobs(namespace).Create(ctx, &newjob, metav1.CreateOptions{}) - //publish backrest backup event + // publish backrest backup event if cmd == "backup" { topics := make([]string, 1) topics[0] = events.EventTopicBackup @@ -151,7 +155,6 @@ func Backrest(namespace string, clientset kubernetes.Interface, task *crv1.Pgtas log.Error(err.Error()) } } - } // CreateInitialBackup creates a Pgtask in order to initiate the initial pgBackRest backup for a cluster @@ -244,7 +247,7 @@ func CleanBackupResources(clientset kubeapi.Interface, namespace, clusterName st return err } - //remove previous backup job + // remove previous backup job selector := config.LABEL_BACKREST_COMMAND + "=" + crv1.PgtaskBackrestBackup + "," + config.LABEL_PG_CLUSTER + "=" + clusterName + "," + config.LABEL_BACKREST + "=true" deletePropagation := metav1.DeletePropagationForeground @@ -257,27 +260,26 @@ func CleanBackupResources(clientset kubeapi.Interface, namespace, clusterName st log.Error(err) } - timeout := time.After(30 * time.Second) - tick := time.NewTicker(1 * time.Second) - defer tick.Stop() - for { - select { - case <-timeout: + if err := wait.Poll(1*time.Second, 30*time.Second, func() (bool, error) { + jobList, err := clientset. + BatchV1().Jobs(namespace). + List(ctx, metav1.ListOptions{LabelSelector: selector}) + if err != nil { + log.Error(err) + return false, err + } + + return len(jobList.Items) == 0, nil + }); err != nil { + if errors.Is(err, wait.ErrWaitTimeout) { return fmt.Errorf("Timed out waiting for deletion of pgBackRest backup job for "+ "cluster %s", clusterName) - case <-tick.C: - jobList, err := clientset. - BatchV1().Jobs(namespace). - List(ctx, metav1.ListOptions{LabelSelector: selector}) - if err != nil { - log.Error(err) - return err - } - if len(jobList.Items) == 0 { - return nil - } } + + return err } + + return nil } // getCommandOptsFromPod adds command line options from the primary pod to a backrest job. diff --git a/internal/operator/cluster/clusterlogic.go b/internal/operator/cluster/clusterlogic.go index f0c34c2007..7acf0fe41c 100644 --- a/internal/operator/cluster/clusterlogic.go +++ b/internal/operator/cluster/clusterlogic.go @@ -43,6 +43,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes" ) @@ -259,7 +260,6 @@ func getBootstrapJobFields(clientset kubeapi.Interface, func getClusterDeploymentFields(clientset kubernetes.Interface, cl *crv1.Pgcluster, dataVolume, walVolume operator.StorageResult, tablespaceVolumes map[string]operator.StorageResult) operator.DeploymentTemplateFields { - namespace := cl.GetNamespace() log.Infof("creating Pgcluster %s in namespace %s", cl.Name, namespace) @@ -293,7 +293,7 @@ func getClusterDeploymentFields(clientset kubernetes.Interface, supplementalGroups = append(supplementalGroups, v.SupplementalGroups...) } - //create the primary deployment + // create the primary deployment deploymentFields := operator.DeploymentTemplateFields{ Name: cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY], IsInit: true, @@ -343,12 +343,11 @@ func getClusterDeploymentFields(clientset kubernetes.Interface, // DeleteCluster ... func DeleteCluster(clientset kubernetes.Interface, cl *crv1.Pgcluster, namespace string) error { - var err error log.Info("deleting Pgcluster object" + " in namespace " + namespace) log.Info("deleting with Name=" + cl.Spec.Name + " in namespace " + namespace) - //create rmdata job + // create rmdata job isReplica := false isBackup := false removeData := true @@ -362,7 +361,6 @@ func DeleteCluster(clientset kubernetes.Interface, cl *crv1.Pgcluster, namespace } return err - } // scaleReplicaCreateMissingService creates a service for cluster replicas if @@ -412,7 +410,7 @@ func scaleReplicaCreateDeployment(clientset kubernetes.Interface, var replicaDoc bytes.Buffer serviceName := replica.Spec.ClusterName + "-replica" - //replicaFlag := true + // replicaFlag := true // replicaLabels := operator.GetPrimaryLabels(serviceName, replica.Spec.ClusterName, replicaFlag, cluster.Spec.UserLabels) cluster.Spec.UserLabels[config.LABEL_REPLICA_NAME] = replica.Spec.Name @@ -424,13 +422,13 @@ func scaleReplicaCreateDeployment(clientset kubernetes.Interface, archiveMode = "on" } if cluster.Labels[config.LABEL_BACKREST] == "true" { - //backrest requires archive mode be set to on + // backrest requires archive mode be set to on archiveMode = "on" } image := cluster.Spec.CCPImage - //check for --ccp-image-tag at the command line + // check for --ccp-image-tag at the command line imageTag := cluster.Spec.CCPImageTag if replica.Spec.UserLabels[config.LABEL_CCP_IMAGE_TAG_KEY] != "" { imageTag = replica.Spec.UserLabels[config.LABEL_CCP_IMAGE_TAG_KEY] @@ -448,7 +446,7 @@ func scaleReplicaCreateDeployment(clientset kubernetes.Interface, supplementalGroups = append(supplementalGroups, v.SupplementalGroups...) } - //create the replica deployment + // create the replica deployment replicaDeploymentFields := operator.DeploymentTemplateFields{ Name: replica.Spec.Name, ClusterName: replica.Spec.ClusterName, @@ -550,7 +548,6 @@ func DeleteReplica(clientset kubernetes.Interface, cl *crv1.Pgreplica, namespace }) return err - } func publishScaleError(namespace string, username string, cluster *crv1.Pgcluster) { @@ -625,7 +622,6 @@ func ShutdownCluster(clientset kubeapi.Interface, cluster crv1.Pgcluster) error // only consider pods that are running pods, err := clientset.CoreV1().Pods(cluster.Namespace).List(ctx, options) - if err != nil { return err } @@ -695,7 +691,6 @@ func ShutdownCluster(clientset kubeapi.Interface, cluster crv1.Pgcluster) error // includes changing the replica count for all clusters to 1, and then updating the pgcluster // with a shutdown status. func StartupCluster(clientset kubernetes.Interface, cluster crv1.Pgcluster) error { - log.Debugf("Cluster Operator: starting cluster %s", cluster.Name) // ensure autofailover is enabled to ensure proper startup of the cluster @@ -804,22 +799,17 @@ func waitForDeploymentReady(clientset kubernetes.Interface, namespace, deploymen ctx := context.TODO() // set up the timer and timeout - // first, ensure that there is an available Pod - timeout := time.After(timeoutSecs) - tick := time.NewTicker(periodSecs) - defer tick.Stop() - - for { - select { - case <-timeout: - return fmt.Errorf("readiness timeout reached for deployment %q", deploymentName) - case <-tick.C: - // check to see if the deployment is ready - if d, err := clientset.AppsV1().Deployments(namespace).Get(ctx, deploymentName, metav1.GetOptions{}); err != nil { - log.Warn(err) - } else if d.Status.Replicas == d.Status.ReadyReplicas { - return nil - } + if err := wait.Poll(periodSecs, timeoutSecs, func() (bool, error) { + // check to see if the deployment is ready + d, err := clientset.AppsV1().Deployments(namespace).Get(ctx, deploymentName, metav1.GetOptions{}) + if err != nil { + log.Warn(err) } + + return err == nil && d.Status.Replicas == d.Status.ReadyReplicas, nil + }); err != nil { + return fmt.Errorf("readiness timeout reached for deployment %q", deploymentName) } + + return nil } diff --git a/internal/operator/cluster/rolling.go b/internal/operator/cluster/rolling.go index 9f5351d7b0..feb2df24b1 100644 --- a/internal/operator/cluster/rolling.go +++ b/internal/operator/cluster/rolling.go @@ -31,6 +31,7 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) @@ -306,26 +307,21 @@ func waitForPostgresInstance(clientset kubernetes.Interface, restConfig *rest.Co pod := pods.Items[0] cmd := generatePostgresReadyCommand(cluster.Spec.Port) - // set up the timer and timeout - // first, ensure that there is an available Pod - timeout := time.After(timeoutSecs) - tick := time.NewTicker(periodSecs) - defer tick.Stop() - - for { - select { - case <-timeout: - return fmt.Errorf("readiness timeout reached for start up of cluster %q instance %q", cluster.Name, deployment.Name) - case <-tick.C: - // check to see if PostgreSQL is ready to accept connections - s, _, _ := kubeapi.ExecToPodThroughAPI(restConfig, clientset, - cmd, "database", pod.Name, pod.Namespace, nil) - - // really we should find a way to get the exit code in the future, but - // in the interim... - if strings.Contains(s, "accepting connections") { - return nil - } - } + // start polling to test if the Postgres instance is available to accept + // connections + if err := wait.Poll(periodSecs, timeoutSecs, func() (bool, error) { + // check to see if PostgreSQL is ready to accept connections + s, _, _ := kubeapi.ExecToPodThroughAPI(restConfig, clientset, + cmd, "database", pod.Name, pod.Namespace, nil) + + // really we should find a way to get the exit code in the future, but + // in the interim, we know that we can accept connections if the below + // string is present + return strings.Contains(s, "accepting connections"), nil + }); err != nil { + return fmt.Errorf("readiness timeout reached for start up of cluster %q instance %q", + cluster.Name, deployment.Name) } + + return nil } diff --git a/internal/operator/cluster/upgrade.go b/internal/operator/cluster/upgrade.go index d497753c28..084f828505 100644 --- a/internal/operator/cluster/upgrade.go +++ b/internal/operator/cluster/upgrade.go @@ -36,6 +36,7 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes" "sigs.k8s.io/yaml" ) @@ -131,7 +132,6 @@ func AddUpgrade(clientset kubeapi.Interface, upgrade *crv1.Pgtask, namespace str PublishUpgradeEvent(events.EventUpgradeClusterCreateSubmitted, namespace, upgrade, "") log.Debugf("finished main upgrade workflow for cluster: %s", upgradeTargetClusterName) - } // getPrimaryPodDeploymentName searches through the pods associated with this pgcluster for the 'primary' pod, @@ -151,7 +151,6 @@ func getPrimaryPodDeploymentName(clientset kubernetes.Interface, cluster *crv1.P // only consider pods that are running pods, err := clientset.CoreV1().Pods(cluster.Namespace).List(ctx, options) - if err != nil { log.Errorf("no pod with the primary role label was found for cluster %s. Error: %s", cluster.Name, err.Error()) return "" @@ -251,7 +250,6 @@ func handleReplicas(clientset kubeapi.Interface, clusterName, currentPrimaryPVC, // (e.g. pgo create cluster hippo --replica-count=2) but will not included any replicas // created using the 'pgo scale' command func SetReplicaNumber(pgcluster *crv1.Pgcluster, numReplicas string) { - pgcluster.Spec.Replicas = numReplicas } @@ -280,10 +278,12 @@ func deleteBeforeUpgrade(clientset kubeapi.Interface, clusterName, currentPrimar } // wait until the backrest shared repo pod deployment has been deleted before continuing - waitStatus := deploymentWait(clientset, namespace, clusterName+"-backrest-shared-repo", 180, 10) + waitStatus := deploymentWait(clientset, namespace, clusterName+"-backrest-shared-repo", + 180*time.Second, 10*time.Second) log.Debug(waitStatus) // wait until the primary pod deployment has been deleted before continuing - waitStatus = deploymentWait(clientset, namespace, currentPrimary, 180, 10) + waitStatus = deploymentWait(clientset, namespace, currentPrimary, + 180*time.Second, 10*time.Second) log.Debug(waitStatus) // delete the pgcluster @@ -318,21 +318,15 @@ func deleteBeforeUpgrade(clientset kubeapi.Interface, clusterName, currentPrimar // deletion to complete before proceeding with the rest of the pgcluster upgrade. func deploymentWait(clientset kubernetes.Interface, namespace, deploymentName string, timeoutSecs, periodSecs time.Duration) string { ctx := context.TODO() - timeout := time.After(timeoutSecs * time.Second) - tick := time.NewTicker(periodSecs * time.Second) - defer tick.Stop() - - for { - select { - case <-timeout: - return fmt.Sprintf("Timed out waiting for deployment to be deleted: [%s]", deploymentName) - case <-tick.C: - _, err := clientset.AppsV1().Deployments(namespace).Get(ctx, deploymentName, metav1.GetOptions{}) - if err != nil { - return fmt.Sprintf("Deployment %s has been deleted.", deploymentName) - } - } + + if err := wait.Poll(periodSecs, timeoutSecs, func() (bool, error) { + _, err := clientset.AppsV1().Deployments(namespace).Get(ctx, deploymentName, metav1.GetOptions{}) + return err != nil, nil + }); err != nil { + return fmt.Sprintf("Timed out waiting for deployment to be deleted: [%s]", deploymentName) } + + return fmt.Sprintf("Deployment %s has been deleted.", deploymentName) } // deleteNonupgradePgtasks deletes all existing pgtasks by selector with the exception of the @@ -459,7 +453,6 @@ func recreateBackrestRepoSecret(clientset kubernetes.Interface, clustername, nam // for the current Postgres Operator version, updating or deleting values where appropriate, and sets // an expected status so that the CRD object can be recreated. func preparePgclusterForUpgrade(pgcluster *crv1.Pgcluster, parameters map[string]string, oldpgoversion, currentPrimary string) { - // first, update the PGO version references to the current Postgres Operator version pgcluster.ObjectMeta.Labels[config.LABEL_PGO_VERSION] = parameters[config.LABEL_PGO_VERSION] pgcluster.Spec.UserLabels[config.LABEL_PGO_VERSION] = parameters[config.LABEL_PGO_VERSION] From e42fe12699a6bfeba768e625e5ba892c6aacd14f Mon Sep 17 00:00:00 2001 From: tjmoore4 <42497036+tjmoore4@users.noreply.github.com> Date: Fri, 11 Dec 2020 11:27:24 -0500 Subject: [PATCH 041/276] Remove references to crunchy-backrest-restore The functionality of the crunchy-backrest-restore container is now included in the new crunchy-pgbackrest. As such, the existing reference to the obsolete container can now be removed. --- installers/olm/postgresoperator.csv.images.yaml | 1 - internal/config/images.go | 2 -- 2 files changed, 3 deletions(-) diff --git a/installers/olm/postgresoperator.csv.images.yaml b/installers/olm/postgresoperator.csv.images.yaml index 97aa48299c..21d1f3c10f 100644 --- a/installers/olm/postgresoperator.csv.images.yaml +++ b/installers/olm/postgresoperator.csv.images.yaml @@ -11,7 +11,6 @@ - { name: RELATED_IMAGE_CRUNCHY_POSTGRES_EXPORTER, value: '${PGO_IMAGE_PREFIX}/crunchy-postgres-exporter:${PGO_IMAGE_TAG}' } - { name: RELATED_IMAGE_CRUNCHY_ADMIN, value: '${CCP_IMAGE_PREFIX}/crunchy-admin:${CCP_IMAGE_TAG}' } -- { name: RELATED_IMAGE_CRUNCHY_BACKREST_RESTORE, value: '${CCP_IMAGE_PREFIX}/crunchy-backrest-restore:${CCP_IMAGE_TAG}' } - { name: RELATED_IMAGE_CRUNCHY_PGADMIN, value: '${CCP_IMAGE_PREFIX}/crunchy-pgadmin4:${CCP_IMAGE_TAG}' } - { name: RELATED_IMAGE_CRUNCHY_PGBADGER, value: '${CCP_IMAGE_PREFIX}/crunchy-pgbadger:${CCP_IMAGE_TAG}' } - { name: RELATED_IMAGE_CRUNCHY_PGBOUNCER, value: '${CCP_IMAGE_PREFIX}/crunchy-pgbouncer:${CCP_IMAGE_TAG}' } diff --git a/internal/config/images.go b/internal/config/images.go index 2811e927fc..3c7fdf4285 100644 --- a/internal/config/images.go +++ b/internal/config/images.go @@ -22,7 +22,6 @@ const ( CONTAINER_IMAGE_PGO_CLIENT = "pgo-client" CONTAINER_IMAGE_PGO_RMDATA = "pgo-rmdata" CONTAINER_IMAGE_CRUNCHY_ADMIN = "crunchy-admin" - CONTAINER_IMAGE_CRUNCHY_BACKREST_RESTORE = "crunchy-backrest-restore" CONTAINER_IMAGE_CRUNCHY_POSTGRES_EXPORTER = "crunchy-postgres-exporter" CONTAINER_IMAGE_CRUNCHY_GRAFANA = "crunchy-grafana" CONTAINER_IMAGE_CRUNCHY_PGADMIN = "crunchy-pgadmin4" @@ -44,7 +43,6 @@ var RelatedImageMap = map[string]string{ "RELATED_IMAGE_PGO_CLIENT": CONTAINER_IMAGE_PGO_CLIENT, "RELATED_IMAGE_PGO_RMDATA": CONTAINER_IMAGE_PGO_RMDATA, "RELATED_IMAGE_CRUNCHY_ADMIN": CONTAINER_IMAGE_CRUNCHY_ADMIN, - "RELATED_IMAGE_CRUNCHY_BACKREST_RESTORE": CONTAINER_IMAGE_CRUNCHY_BACKREST_RESTORE, "RELATED_IMAGE_CRUNCHY_POSTGRES_EXPORTER": CONTAINER_IMAGE_CRUNCHY_POSTGRES_EXPORTER, "RELATED_IMAGE_CRUNCHY_PGADMIN": CONTAINER_IMAGE_CRUNCHY_PGADMIN, "RELATED_IMAGE_CRUNCHY_PGBADGER": CONTAINER_IMAGE_CRUNCHY_PGBADGER, From 2155584f2367251a4d3d3aed3223a444a38912bd Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Wed, 16 Dec 2020 09:43:40 -0500 Subject: [PATCH 042/276] Do not consider evicted Pods with `pgo df` For a variety of reasons, including the need to exec into Pods to get PVC status with `pgo df`, only running Pods should be considered for this command and, in particular, no evicted Pods. Issue: [ch9959] Issue: #2129 --- internal/apiserver/dfservice/dfimpl.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/internal/apiserver/dfservice/dfimpl.go b/internal/apiserver/dfservice/dfimpl.go index 0b2ac196af..5a0186f41b 100644 --- a/internal/apiserver/dfservice/dfimpl.go +++ b/internal/apiserver/dfservice/dfimpl.go @@ -25,10 +25,12 @@ import ( "github.com/crunchydata/postgres-operator/internal/kubeapi" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" + log "github.com/sirupsen/logrus" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" "k8s.io/client-go/kubernetes" ) @@ -150,7 +152,12 @@ func getClusterDf(cluster *crv1.Pgcluster, clusterResultsChannel chan msgs.DfDet selector := fmt.Sprintf("%s=%s,!%s", config.LABEL_PG_CLUSTER, cluster.Spec.Name, config.LABEL_PGHA_BOOTSTRAP) - pods, err := apiserver.Clientset.CoreV1().Pods(cluster.Spec.Namespace).List(ctx, metav1.ListOptions{LabelSelector: selector}) + options := metav1.ListOptions{ + FieldSelector: fields.OneTermEqualSelector("status.phase", string(v1.PodRunning)).String(), + LabelSelector: selector, + } + + pods, err := apiserver.Clientset.CoreV1().Pods(cluster.Spec.Namespace).List(ctx, options) // if there is an error attempting to get the pods, just return if err != nil { From fdef98956f763643867fb36f4126afc9659de52e Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 13 Dec 2020 14:33:30 -0500 Subject: [PATCH 043/276] Ignore a pgBouncer not found error when updating annotations pgBouncer is an optional deployment, as such, we should proceed on if the pgBouncer is not found. --- internal/controller/pgcluster/pgclustercontroller.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/controller/pgcluster/pgclustercontroller.go b/internal/controller/pgcluster/pgclustercontroller.go index d363445106..82111a11c0 100644 --- a/internal/controller/pgcluster/pgclustercontroller.go +++ b/internal/controller/pgcluster/pgclustercontroller.go @@ -34,6 +34,7 @@ import ( informers "github.com/crunchydata/postgres-operator/pkg/generated/informers/externalversions/crunchydata.com/v1" log "github.com/sirupsen/logrus" + kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/cache" @@ -380,7 +381,7 @@ func updateAnnotations(c *Controller, oldCluster *crv1.Pgcluster, newCluster *cr } if len(annotationsPgBouncer) != 0 { - if err := clusteroperator.UpdatePgBouncerAnnotations(c.Client, newCluster, annotationsPgBouncer); err != nil { + if err := clusteroperator.UpdatePgBouncerAnnotations(c.Client, newCluster, annotationsPgBouncer); err != nil && !kerrors.IsNotFound(err) { return err } } From 86c1b7d1ec132480f30f1a4dada8e9a0555b9f8e Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 13 Dec 2020 15:15:13 -0500 Subject: [PATCH 044/276] Aggregate rolling update triggered behavior Updates to a PostgreSQL cluster that warrant a rolling update are now aggregated to only trigger a single rolling update per action taken on a PostgreSQL cluster. This allows for the changes to be rolled out more rapidly, as well as limit the number of downtime events that need to take place. --- .../pgcluster/pgclustercontroller.go | 58 +++++++++++++------ 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/internal/controller/pgcluster/pgclustercontroller.go b/internal/controller/pgcluster/pgclustercontroller.go index 82111a11c0..701429e576 100644 --- a/internal/controller/pgcluster/pgclustercontroller.go +++ b/internal/controller/pgcluster/pgclustercontroller.go @@ -34,6 +34,7 @@ import ( informers "github.com/crunchydata/postgres-operator/pkg/generated/informers/externalversions/crunchydata.com/v1" log "github.com/sirupsen/logrus" + appsv1 "k8s.io/api/apps/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -176,6 +177,9 @@ func (c *Controller) processNextItem() bool { func (c *Controller) onUpdate(oldObj, newObj interface{}) { oldcluster := oldObj.(*crv1.Pgcluster) newcluster := newObj.(*crv1.Pgcluster) + // initialize a slice that may contain functions that need to be executed + // as part of a rolling update + rollingUpdateFuncs := [](func(*crv1.Pgcluster, *appsv1.Deployment) error){} log.Debugf("pgcluster onUpdate for cluster %s (namespace %s)", newcluster.ObjectMeta.Namespace, newcluster.ObjectMeta.Name) @@ -239,10 +243,7 @@ func (c *Controller) onUpdate(oldObj, newObj interface{}) { !reflect.DeepEqual(oldcluster.Spec.Limits, newcluster.Spec.Limits) || !reflect.DeepEqual(oldcluster.Spec.ExporterResources, newcluster.Spec.ExporterResources) || !reflect.DeepEqual(oldcluster.Spec.ExporterLimits, newcluster.Spec.ExporterLimits) { - if err := clusteroperator.RollingUpdate(c.Client, c.Client.Config, newcluster, clusteroperator.UpdateResources); err != nil { - log.Error(err) - return - } + rollingUpdateFuncs = append(rollingUpdateFuncs, clusteroperator.UpdateResources) } // see if any of the pgBackRest repository resource values have changed, and @@ -271,16 +272,40 @@ func (c *Controller) onUpdate(oldObj, newObj interface{}) { log.Error(err) return } + rollingUpdateFuncs = append(rollingUpdateFuncs, clusteroperator.UpdateTablespaces) } // check to see if any of the annotations have been modified, in particular, // the non-system annotations if !reflect.DeepEqual(oldcluster.Spec.Annotations, newcluster.Spec.Annotations) { - if err := updateAnnotations(c, oldcluster, newcluster); err != nil { + if changed, err := updateAnnotations(c, oldcluster, newcluster); err != nil { log.Error(err) return + } else if changed { + // append the PostgreSQL specific functions as part of a rolling update + rollingUpdateFuncs = append(rollingUpdateFuncs, clusteroperator.UpdateAnnotations) } } + + // if there is no need to perform a rolling update, exit here + if len(rollingUpdateFuncs) == 0 { + return + } + + // otherwise, create an anonymous function that executes each of the rolling + // update functions as part of the rolling update + if err := clusteroperator.RollingUpdate(c.Client, c.Client.Config, newcluster, + func(cluster *crv1.Pgcluster, deployment *appsv1.Deployment) error { + for _, fn := range rollingUpdateFuncs { + if err := fn(cluster, deployment); err != nil { + return err + } + } + return nil + }); err != nil { + log.Error(err) + return + } } // onDelete is called when a pgcluster is deleted @@ -317,10 +342,13 @@ func addIdentifier(clusterCopy *crv1.Pgcluster) { // deployments, which includes: // // - globally applied annotations -// - postgres instance specific annotations // - pgBackRest instance specific annotations // - pgBouncer instance specific annotations -func updateAnnotations(c *Controller, oldCluster *crv1.Pgcluster, newCluster *crv1.Pgcluster) error { +// +// The Postgres specific annotations need to be handled by the caller function, +// due to the fact they need to be applied in a rolling update manner that can +// be controlled. We indicate this to the calling function by returning "true" +func updateAnnotations(c *Controller, oldCluster *crv1.Pgcluster, newCluster *crv1.Pgcluster) (bool, error) { // so we have a two-tier problem we need to solve: // 1. Which of the deployment types are being modified (or in the case of // global, all of them)? @@ -376,23 +404,17 @@ func updateAnnotations(c *Controller, oldCluster *crv1.Pgcluster, newCluster *cr // but only do so if we have to if len(annotationsBackrest) != 0 { if err := backrestoperator.UpdateAnnotations(c.Client, newCluster, annotationsBackrest); err != nil { - return err + return false, err } } if len(annotationsPgBouncer) != 0 { if err := clusteroperator.UpdatePgBouncerAnnotations(c.Client, newCluster, annotationsPgBouncer); err != nil && !kerrors.IsNotFound(err) { - return err - } - } - - if len(annotationsPostgres) != 0 { - if err := clusteroperator.RollingUpdate(c.Client, c.Client.Config, newCluster, clusteroperator.UpdateAnnotations); err != nil { - return err + return false, err } } - return nil + return len(annotationsPostgres) != 0, nil } // updatePgBouncer updates the pgBouncer Deployment to reflect any changes that @@ -460,9 +482,7 @@ func updateTablespaces(c *Controller, oldCluster *crv1.Pgcluster, newCluster *cr } } - // alright, update the tablespace entries for this cluster! - // if it returns an error, pass the error back up to the caller - return clusteroperator.RollingUpdate(c.Client, c.Client.Config, newCluster, clusteroperator.UpdateTablespaces) + return nil } // WorkerCount returns the worker count for the controller From 4a8266e4e31d7fa8821ea131187687b531a05824 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 13 Dec 2020 19:29:39 -0500 Subject: [PATCH 045/276] Modify enablement of a metrics-enabled PostgreSQL cluster This adds a CRD attribute to pgcluster called `exporter`, which will ultimately allow for the toggling on/off of the metrics sidecar within a PostgreSQL cluster. Includes an upgrade path for eliminating confusing labels for the enablement of the exporter. --- .../apiserver/clusterservice/clusterimpl.go | 12 +++---- internal/config/labels.go | 1 - internal/operator/cluster/clusterlogic.go | 24 +++++++------- internal/operator/cluster/upgrade.go | 14 ++++++++ internal/operator/clusterutilities.go | 32 ++++++++++--------- pkg/apis/crunchydata.com/v1/cluster.go | 4 ++- 6 files changed, 52 insertions(+), 35 deletions(-) diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index 5c51d98514..c411e8b498 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -721,13 +721,6 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. userLabelsMap[config.LABEL_CUSTOM_CONFIG] = request.CustomConfig } - //set the metrics flag with the global setting first - userLabelsMap[config.LABEL_EXPORTER] = strconv.FormatBool(apiserver.MetricsFlag) - - //if metrics is chosen on the pgo command, stick it into the user labels - if request.MetricsFlag { - userLabelsMap[config.LABEL_EXPORTER] = "true" - } if request.ServiceType != "" { if request.ServiceType != config.DEFAULT_SERVICE_TYPE && request.ServiceType != config.LOAD_BALANCER_SERVICE_TYPE && request.ServiceType != config.NODEPORT_SERVICE_TYPE { resp.Status.Code = msgs.Error @@ -1138,6 +1131,11 @@ func getClusterParams(request *msgs.CreateClusterRequest, name string, userLabel spec.CustomConfig = userLabelsMap[config.LABEL_CUSTOM_CONFIG] } + // enable the exporter sidecar based on the what the user based in or what + // the default value is. the user value takes precedence, unless it's false, + // as the legacy check only looked for enablement + spec.Exporter = request.MetricsFlag || apiserver.MetricsFlag + // if the request has overriding CPU/Memory requests/limits parameters, // these will take precedence over the defaults if request.CPULimit != "" { diff --git a/internal/config/labels.go b/internal/config/labels.go index eb5522b092..4b540a5227 100644 --- a/internal/config/labels.go +++ b/internal/config/labels.go @@ -37,7 +37,6 @@ const LABEL_INGEST = "ingest" const LABEL_PGREMOVE = "pgremove" const LABEL_PVCNAME = "pvcname" const LABEL_EXPORTER = "crunchy-postgres-exporter" -const LABEL_EXPORTER_PG_USER = "ccp_monitoring" const LABEL_ARCHIVE = "archive" const LABEL_ARCHIVE_TIMEOUT = "archive-timeout" const LABEL_CUSTOM_CONFIG = "custom-config" diff --git a/internal/operator/cluster/clusterlogic.go b/internal/operator/cluster/clusterlogic.go index 7acf0fe41c..ded0c0e025 100644 --- a/internal/operator/cluster/clusterlogic.go +++ b/internal/operator/cluster/clusterlogic.go @@ -71,19 +71,11 @@ func addClusterCreateMissingService(clientset kubernetes.Interface, cl *crv1.Pgc serviceFields.PGBadgerPort = cl.Spec.PGBadgerPort } - // ...due to legacy reasons, the exporter label may not be available yet in the - // main labels. so we will check here first, and then check the user labels - if val, ok := clusterLabels[config.LABEL_EXPORTER]; ok && val == config.LABEL_TRUE { + // set the exporter port if exporter is enabled + if cl.Spec.Exporter { serviceFields.ExporterPort = cl.Spec.ExporterPort } - // ...this condition should be targeted for removal in the future - if cl.Spec.UserLabels != nil { - if val, ok := cl.Spec.UserLabels[config.LABEL_EXPORTER]; ok && val == config.LABEL_TRUE { - serviceFields.ExporterPort = cl.Spec.ExporterPort - } - } - return CreateService(clientset, &serviceFields, namespace) } @@ -283,6 +275,11 @@ func getClusterDeploymentFields(clientset kubernetes.Interface, // 'crunchy-pgha-scope' label on the pgcluster cl.Spec.UserLabels[config.LABEL_PGHA_SCOPE] = cl.Spec.Name + // Set the exporter labels, if applicable + if cl.Spec.Exporter { + cl.Spec.UserLabels[config.LABEL_EXPORTER] = config.LABEL_TRUE + } + // set up a map of the names of the tablespaces as well as the storage classes tablespaceStorageTypeMap := operator.GetTablespaceStorageTypeMap(cl.Spec.TablespaceMounts) @@ -319,7 +316,7 @@ func getClusterDeploymentFields(clientset kubernetes.Interface, ConfVolume: operator.GetConfVolume(clientset, cl, namespace), ExporterAddon: operator.GetExporterAddon(clientset, namespace, &cl.Spec), BadgerAddon: operator.GetBadgerAddon(clientset, namespace, cl, cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY]), - PgmonitorEnvVars: operator.GetPgmonitorEnvVars(cl.Spec.UserLabels[config.LABEL_EXPORTER], cl.Spec.CollectSecretName), + PgmonitorEnvVars: operator.GetPgmonitorEnvVars(cl), ScopeLabel: config.LABEL_PGHA_SCOPE, PgbackrestEnvVars: operator.GetPgbackrestEnvVars(cl, cl.Labels[config.LABEL_BACKREST], cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY], cl.Spec.Port, cl.Spec.UserLabels[config.LABEL_BACKREST_STORAGE_TYPE]), @@ -436,6 +433,11 @@ func scaleReplicaCreateDeployment(clientset kubernetes.Interface, cluster.Spec.UserLabels[config.LABEL_DEPLOYMENT_NAME] = replica.Spec.Name + // Set the exporter labels, if applicable + if cluster.Spec.Exporter { + cluster.Spec.UserLabels[config.LABEL_EXPORTER] = config.LABEL_TRUE + } + // set up a map of the names of the tablespaces as well as the storage classes tablespaceStorageTypeMap := operator.GetTablespaceStorageTypeMap(cluster.Spec.TablespaceMounts) diff --git a/internal/operator/cluster/upgrade.go b/internal/operator/cluster/upgrade.go index 084f828505..ecbd9d9985 100644 --- a/internal/operator/cluster/upgrade.go +++ b/internal/operator/cluster/upgrade.go @@ -459,6 +459,8 @@ func preparePgclusterForUpgrade(pgcluster *crv1.Pgcluster, parameters map[string // next, capture the existing Crunchy Postgres Exporter configuration settings (previous to version // 4.5.0 referred to as Crunchy Collect), if they exist, and store them in the current labels + // 4.6.0 added this value to the spec as "Exporter", so the next step ensure + // that the value is migrated over if value, ok := pgcluster.ObjectMeta.Labels["crunchy_collect"]; ok { pgcluster.ObjectMeta.Labels[config.LABEL_EXPORTER] = value delete(pgcluster.ObjectMeta.Labels, "crunchy_collect") @@ -469,6 +471,18 @@ func preparePgclusterForUpgrade(pgcluster *crv1.Pgcluster, parameters map[string delete(pgcluster.Spec.UserLabels, "crunchy_collect") } + // convert the metrics label over to using a proper definition. Give the user + // label precedence. + if value, ok := pgcluster.ObjectMeta.Labels[config.LABEL_EXPORTER]; ok { + pgcluster.Spec.Exporter, _ = strconv.ParseBool(value) + delete(pgcluster.ObjectMeta.Labels, config.LABEL_EXPORTER) + } + + if value, ok := pgcluster.Spec.UserLabels[config.LABEL_EXPORTER]; ok { + pgcluster.Spec.Exporter, _ = strconv.ParseBool(value) + delete(pgcluster.Spec.UserLabels, config.LABEL_EXPORTER) + } + // since the current primary label is not used in this version of the Postgres Operator, // delete it before moving on to other upgrade tasks delete(pgcluster.ObjectMeta.Labels, config.LABEL_CURRENT_PRIMARY) diff --git a/internal/operator/clusterutilities.go b/internal/operator/clusterutilities.go index d4ae78706b..2fb2277330 100644 --- a/internal/operator/clusterutilities.go +++ b/internal/operator/clusterutilities.go @@ -359,11 +359,11 @@ func GetBadgerAddon(clientset kubernetes.Interface, namespace string, cluster *c func GetExporterAddon(clientset kubernetes.Interface, namespace string, spec *crv1.PgclusterSpec) string { - if spec.UserLabels[config.LABEL_EXPORTER] == "true" { + if spec.Exporter { log.Debug("crunchy-postgres-exporter was found as a label on cluster create") log.Debugf("creating exporter secret for cluster %s", spec.Name) - err := util.CreateSecret(clientset, spec.Name, spec.CollectSecretName, config.LABEL_EXPORTER_PG_USER, + err := util.CreateSecret(clientset, spec.Name, spec.CollectSecretName, crv1.PGUserMonitor, Pgo.Cluster.PgmonitorPassword, namespace) if err != nil { log.Error(err) @@ -769,21 +769,23 @@ func GetPodAntiAffinityType(cluster *crv1.Pgcluster, deploymentType crv1.PodAnti // GetPgmonitorEnvVars populates the pgmonitor env var template, which contains any // pgmonitor env vars that need to be included in the Deployment spec for a PG cluster. -func GetPgmonitorEnvVars(metricsEnabled, exporterSecret string) string { - if metricsEnabled == "true" { - fields := PgmonitorEnvVarsTemplateFields{ - ExporterSecret: exporterSecret, - } +func GetPgmonitorEnvVars(cluster *crv1.Pgcluster) string { + if !cluster.Spec.Exporter { + return "" + } - var doc bytes.Buffer - err := config.PgmonitorEnvVarsTemplate.Execute(&doc, fields) - if err != nil { - log.Error(err.Error()) - return "" - } - return doc.String() + fields := PgmonitorEnvVarsTemplateFields{ + ExporterSecret: cluster.Spec.CollectSecretName, } - return "" + + doc := bytes.Buffer{} + + if err := config.PgmonitorEnvVarsTemplate.Execute(&doc, fields); err != nil { + log.Error(err) + return "" + } + + return doc.String() } // GetPgbackrestS3EnvVars retrieves the values for the various configuration settings require to diff --git a/pkg/apis/crunchydata.com/v1/cluster.go b/pkg/apis/crunchydata.com/v1/cluster.go index 91b7f8dad8..63d3914f0f 100644 --- a/pkg/apis/crunchydata.com/v1/cluster.go +++ b/pkg/apis/crunchydata.com/v1/cluster.go @@ -50,7 +50,9 @@ type PgclusterSpec struct { PGOImagePrefix string `json:"pgoimageprefix"` Port string `json:"port"` PGBadgerPort string `json:"pgbadgerport"` - ExporterPort string `json:"exporterport"` + // Exporter, if set to true, enables the exporter sidecar + Exporter bool `json:"exporter"` + ExporterPort string `json:"exporterport"` PrimaryStorage PgStorageSpec WALStorage PgStorageSpec From 554aea609826fd11949dd44f40b12e822728bfe6 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 14 Dec 2020 15:18:24 -0500 Subject: [PATCH 046/276] Improve compatibility between templates and Kubernetes objects The updates the "exporter.json" template, which is used for deploying the "crunchy-postgres-exporter" sidecar for metrics collection in a PostgreSQL cluster, to not have a preceiding "," in it. This in turn allows for the file to be mapped into a Kubernetes Container object, for convenience of manipulation within a program. --- .../pgo-operator/files/pgo-configs/cluster-deployment.json | 5 +++-- .../roles/pgo-operator/files/pgo-configs/exporter.json | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json index 7fd77e6449..b5c823b3fd 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json @@ -234,8 +234,9 @@ ], "imagePullPolicy": "IfNotPresent" }{{ end }} - - {{.ExporterAddon }} + {{ if .ExporterAddon }} + ,{{.ExporterAddon }} + {{ end }} {{.BadgerAddon }} diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/exporter.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/exporter.json index c40a26e5ef..9d430c3c20 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/exporter.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/exporter.json @@ -1,4 +1,4 @@ -,{ +{ "name": "exporter", "image": "{{.PGOImagePrefix}}/crunchy-postgres-exporter:{{.PGOImageTag}}", "ports": [{ From 0f807838d05fba6035d5670ebf14062f715f47db Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 14 Dec 2020 15:20:43 -0500 Subject: [PATCH 047/276] Update cluster Deployment match labels Match labels are immutable objects, and given some potentially mutable labels exists within the match labels for the PostgreSQL Deployment objects, it is necessary to modify this set of labels to use the minimum needed for properly deploying a cluster. This reduces the current set of match labels for a PostgreSQL instance to the following: - vendor - pg-cluster -- the name of the PostgreSQL cluster (group of all instances) - deployment-name -- the name of the PostgreSQL instance - pgo-pg-database --- .../files/pgo-configs/cluster-deployment.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json index b5c823b3fd..0081bb205f 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json @@ -12,10 +12,12 @@ "spec": { "replicas": {{.Replicas}}, "selector": { - "matchLabels": { + "matchLabels": { "vendor": "crunchydata", - {{.DeploymentLabels }} - } + "pg-cluster": "{{.ClusterName}}", + "pgo-pg-database": "true", + "deployment-name": "{{.Name}}" + } }, "template": { "metadata": { From 0ee6b6f3ea73707832320027afcba0892ea58f85 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 14 Dec 2020 15:23:46 -0500 Subject: [PATCH 048/276] Enable / disable the metrics sidecar during cluster lifetime This commit introduces the ability to enable/disable the metrics collection sidecar (`crunchy-postgres-exporter`) during the lifetime of a PostgreSQL cluster. This can be toggled in multiple ways, including: - The `exporter` attribute in pgclusters.crunchydata.com - `pgo update cluster --enable-metrics`, which adds the sidecar - `pgo update cluster --disable-metrics`, which removes the sidecar As adding/removing a sidecar results in modifying a Deployment template, this action will trigger a rolling update of the PostgreSQL cluster in an effort to minimize any downtime. This also has the net effect of moving the "ccp_monitoring" used that is created to being fully managed by the Postgres Operator. The `CollectSecretName` attribute is now removed from the pgcluster CRD, as is the "PgMonitorPassword" attribute from the `pgo-deployer` and other installers. Issue: [ch7270] Issue: #1413 --- bin/crunchy-postgres-exporter/start.sh | 24 +- cmd/pgo/cmd/cluster.go | 7 + cmd/pgo/cmd/update.go | 13 +- .../Configuration/pgo-yaml-configuration.md | 1 - .../architecture/high-availability/_index.md | 1 + docs/content/custom-resources/_index.md | 13 +- docs/content/pgo-client/common-tasks.md | 1 - .../reference/pgo_update_cluster.md | 6 +- .../files/pgo-configs/exporter.json | 4 +- .../apiserver/clusterservice/clusterimpl.go | 14 +- internal/apiserver/userservice/userimpl.go | 11 +- internal/config/pgoconfig.go | 1 - .../pgcluster/pgclustercontroller.go | 20 ++ internal/operator/cluster/cluster.go | 3 + internal/operator/cluster/clusterlogic.go | 15 +- internal/operator/cluster/common.go | 116 ++++++ internal/operator/cluster/common_test.go | 39 +++ internal/operator/cluster/exporter.go | 331 ++++++++++++++++++ internal/operator/cluster/pgbouncer.go | 91 +---- internal/operator/cluster/pgbouncer_test.go | 19 - internal/operator/clusterutilities.go | 67 ++-- internal/operator/common.go | 5 - internal/util/exporter.go | 29 ++ internal/util/exporter_test.go | 32 ++ pkg/apis/crunchydata.com/v1/cluster.go | 1 - pkg/apis/crunchydata.com/v1/common.go | 3 - pkg/apiservermsgs/clustermsgs.go | 21 +- 27 files changed, 697 insertions(+), 191 deletions(-) create mode 100644 internal/operator/cluster/common.go create mode 100644 internal/operator/cluster/common_test.go create mode 100644 internal/operator/cluster/exporter.go create mode 100644 internal/util/exporter.go create mode 100644 internal/util/exporter_test.go diff --git a/bin/crunchy-postgres-exporter/start.sh b/bin/crunchy-postgres-exporter/start.sh index 2a0d543b70..a7397973a9 100755 --- a/bin/crunchy-postgres-exporter/start.sh +++ b/bin/crunchy-postgres-exporter/start.sh @@ -76,19 +76,7 @@ set_default_pg_exporter_env() { trap 'trap_sigterm' SIGINT SIGTERM set_default_postgres_exporter_env - -if [[ ! -v DATA_SOURCE_NAME ]] -then - set_default_pg_exporter_env - if [[ ! -z "${EXPORTER_PG_PARAMS}" ]] - then - EXPORTER_PG_PARAMS="?${EXPORTER_PG_PARAMS}" - fi - export DATA_SOURCE_NAME="postgresql://${EXPORTER_PG_USER}:${EXPORTER_PG_PASSWORD}\ -@${EXPORTER_PG_HOST}:${EXPORTER_PG_PORT}/${EXPORTER_PG_DATABASE}${EXPORTER_PG_PARAMS}" -fi - - +set_default_pg_exporter_env if [[ ! ${#default_exporter_env_vars[@]} -eq 0 ]] then @@ -99,16 +87,16 @@ fi # Check that postgres is accepting connections. echo_info "Waiting for PostgreSQL to be ready.." while true; do - ${PG_DIR?}/bin/pg_isready -d ${DATA_SOURCE_NAME} + ${PG_DIR?}/bin/pg_isready -q -h "${EXPORTER_PG_HOST}" -p "${EXPORTER_PG_PORT}" if [ $? -eq 0 ]; then break fi sleep 2 done -echo_info "Checking if PostgreSQL is accepting queries.." +echo_info "Checking if "${EXPORTER_PG_USER}" is is created.." while true; do - ${PG_DIR?}/bin/psql "${DATA_SOURCE_NAME}" -c "SELECT now();" + PGPASSWORD="${EXPORTER_PG_PASSWORD}" ${PG_DIR?}/bin/psql -q -h "${EXPORTER_PG_HOST}" -p "${EXPORTER_PG_PORT}" -U "${EXPORTER_PG_USER}" -c "SELECT 1;" "${EXPORTER_PG_DATABASE}" if [ $? -eq 0 ]; then break fi @@ -135,7 +123,7 @@ else fi done - VERSION=$(${PG_DIR?}/bin/psql "${DATA_SOURCE_NAME}" -qtAX -c "SELECT current_setting('server_version_num')") + VERSION=$(PGPASSWORD="${EXPORTER_PG_PASSWORD}" ${PG_DIR?}/bin/psql -h "${EXPORTER_PG_HOST}" -p "${EXPORTER_PG_PORT}" -U "${EXPORTER_PG_USER}" -qtAX -c "SELECT current_setting('server_version_num')" "${EXPORTER_PG_DATABASE}") if (( ${VERSION?} >= 90500 )) && (( ${VERSION?} < 90600 )) then if [[ -f ${CONFIG_DIR?}/queries_pg95.yml ]] @@ -231,7 +219,7 @@ sed -i "s/#PGBACKREST_INFO_THROTTLE_MINUTES#/${PGBACKREST_INFO_THROTTLE_MINUTES: PG_OPTIONS="--extend.query-path=${QUERY_DIR?}/queries.yml --web.listen-address=:${POSTGRES_EXPORTER_PORT}" echo_info "Starting postgres-exporter.." -${PG_EXP_HOME?}/postgres_exporter ${PG_OPTIONS?} >>/dev/stdout 2>&1 & +DATA_SOURCE_URI="${EXPORTER_PG_HOST}:${EXPORTER_PG_PORT}/${EXPORTER_PG_DATABASE}?${EXPORTER_PG_PARAMS}" DATA_SOURCE_USER="${EXPORTER_PG_USER}" DATA_SOURCE_PASS="${EXPORTER_PG_PASSWORD}" ${PG_EXP_HOME?}/postgres_exporter ${PG_OPTIONS?} >>/dev/stdout 2>&1 & echo $! > $POSTGRES_EXPORTER_PIDFILE wait diff --git a/cmd/pgo/cmd/cluster.go b/cmd/pgo/cmd/cluster.go index a125bc2b5b..b4bf417697 100644 --- a/cmd/pgo/cmd/cluster.go +++ b/cmd/pgo/cmd/cluster.go @@ -628,6 +628,13 @@ func updateCluster(args []string, ns string) { r.Autofail = msgs.UpdateClusterAutofailDisable } + // check to see if the metrics sidecar needs to be enabled or disabled + if EnableMetrics { + r.Metrics = msgs.UpdateClusterMetricsEnable + } else if DisableMetrics { + r.Metrics = msgs.UpdateClusterMetricsDisable + } + // if the user provided resources for CPU or Memory, validate them to ensure // they are valid Kubernetes values if err := util.ValidateQuantity(r.CPURequest, "cpu"); err != nil { diff --git a/cmd/pgo/cmd/update.go b/cmd/pgo/cmd/update.go index 798ccf36f7..303f64b74b 100644 --- a/cmd/pgo/cmd/update.go +++ b/cmd/pgo/cmd/update.go @@ -29,9 +29,13 @@ var ( // DisableLogin allows a user to disable the ability for a PostgreSQL uesr to // log in DisableLogin bool + // DisableMetrics allows a user to disable metrics collection + DisableMetrics bool // EnableLogin allows a user to enable the ability for a PostgreSQL uesr to // log in EnableLogin bool + // EnableMetrics allows a user to enbale metrics collection + EnableMetrics bool // ExpireUser sets a user to having their password expired ExpireUser bool // PgoroleChangePermissions does something with the pgouser access controls, @@ -83,6 +87,8 @@ func init() { UpdateClusterCmd.Flags().StringVar(&CPULimit, "cpu-limit", "", "Set the number of millicores to limit for the CPU, e.g. "+ "\"100m\" or \"0.1\".") UpdateClusterCmd.Flags().BoolVar(&DisableAutofailFlag, "disable-autofail", false, "Disables autofail capabitilies in the cluster.") + UpdateClusterCmd.Flags().BoolVar(&DisableMetrics, "disable-metrics", false, + "Disable the metrics collection sidecar. May cause brief downtime.") UpdateClusterCmd.Flags().BoolVar(&EnableAutofailFlag, "enable-autofail", false, "Enables autofail capabitilies in the cluster.") UpdateClusterCmd.Flags().StringVar(&MemoryRequest, "memory", "", "Set the amount of RAM to request, e.g. "+ "1GiB.") @@ -107,7 +113,8 @@ func init() { "the Crunchy Postgres Exporter sidecar container.") UpdateClusterCmd.Flags().StringVar(&ExporterMemoryLimit, "exporter-memory-limit", "", "Set the amount of memory to limit for "+ "the Crunchy Postgres Exporter sidecar container.") - + UpdateClusterCmd.Flags().BoolVar(&EnableMetrics, "enable-metrics", false, + "Enable the metrics collection sidecar. May cause brief downtime.") UpdateClusterCmd.Flags().BoolVarP(&EnableStandby, "enable-standby", "", false, "Enables standby mode in the cluster(s) specified.") UpdateClusterCmd.Flags().BoolVar(&Startup, "startup", false, "Restart the database cluster if it "+ @@ -248,6 +255,10 @@ var UpdateClusterCmd = &cobra.Command{ "from has been properly shutdown before proceeding!") } + if EnableMetrics || DisableMetrics { + fmt.Println("Adding or removing a metrics collection sidecar can cause downtime.") + } + if len(Tablespaces) > 0 { fmt.Println("Adding tablespaces can cause downtime.") } diff --git a/docs/content/Configuration/pgo-yaml-configuration.md b/docs/content/Configuration/pgo-yaml-configuration.md index c1b6a894e1..b9467258d3 100644 --- a/docs/content/Configuration/pgo-yaml-configuration.md +++ b/docs/content/Configuration/pgo-yaml-configuration.md @@ -23,7 +23,6 @@ The *pgo.yaml* file is broken into major sections as described below: |User | the PostgreSQL normal user name |Database | the PostgreSQL normal user database |Replicas | the number of cluster replicas to create for newly created clusters, typically users will scale up replicas on the pgo CLI command line but this global value can be set as well -|PgmonitorPassword | the password to use for pgmonitor metrics collection if you specify --metrics when creating a PG cluster |Metrics | boolean, if set to true will cause each new cluster to include crunchy-postgres-exporter as a sidecar container for metrics collection, if set to false (default), users can still add metrics on a cluster-by-cluster basis using the pgo command flag --metrics |Badger | boolean, if set to true will cause each new cluster to include crunchy-pgbadger as a sidecar container for static log analysis, if set to false (default), users can still add pgbadger on a cluster-by-cluster basis using the pgo create cluster command flag --pgbadger |Policies | optional, list of policies to apply to a newly created cluster, comma separated, must be valid policies in the catalog diff --git a/docs/content/architecture/high-availability/_index.md b/docs/content/architecture/high-availability/_index.md index 950b150105..b3dc97f290 100644 --- a/docs/content/architecture/high-availability/_index.md +++ b/docs/content/architecture/high-availability/_index.md @@ -330,4 +330,5 @@ modification to the custom resource: - Memory resource adjustments - CPU resource adjustments - Custom annotation changes +- Enabling/disabling the monitoring sidecar on a PostgreSQL cluster (`--metrics`) - Tablespace additions diff --git a/docs/content/custom-resources/_index.md b/docs/content/custom-resources/_index.md index 9869a29bb1..b39dd3ae0b 100644 --- a/docs/content/custom-resources/_index.md +++ b/docs/content/custom-resources/_index.md @@ -395,7 +395,6 @@ spec: user: hippo userlabels: backrest-storage-type: "s3" - crunchy-postgres-exporter: "false" pg-pod-anti-affinity: "" pgo-version: {{< param operatorVersion >}} usersecretname: ${pgo_cluster_name}-hippo-secret @@ -492,7 +491,6 @@ spec: userlabels: NodeLabelKey: "" NodeLabelValue: "" - crunchy-postgres-exporter: "false" pg-pod-anti-affinity: "" pgo-version: {{< param operatorVersion >}} EOF @@ -502,6 +500,13 @@ kubectl apply -f "${pgo_cluster_name}-${pgo_cluster_replica_suffix}-pgreplica.ya Add this time, removing a replica must be handled through the [`pgo` client]({{< relref "/pgo-client/common-tasks.md#high-availability-scaling-up-down">}}). +### Monitoring + +To enable the [monitoring]({{< relref "/architecture/monitoring.md">}}) +(aka metrics) sidecar using the `crunchy-postgres-exporter` container, you need +to set the `exporter` attribute in `pgclusters.crunchydata.com` custom resource. + + ### Add a Tablespace Tablespaces can be added during the lifetime of a PostgreSQL cluster (tablespaces can be removed as well, but for a detailed explanation as to how, please see the [Tablespaces]({{< relref "/architecture/tablespaces.md">}}) section). @@ -679,12 +684,12 @@ make changes, as described below. | CCPImage | `create` | The name of the PostgreSQL container image to use, e.g. `crunchy-postgres-ha` or `crunchy-postgres-ha-gis`. | | CCPImagePrefix | `create` | If provided, the image prefix (or registry) of the PostgreSQL container image, e.g. `registry.developers.crunchydata.com/crunchydata`. The default is to use the image prefix set in the PostgreSQL Operator configuration. | | CCPImageTag | `create` | The tag of the PostgreSQL container image to use, e.g. `{{< param centosBase >}}-{{< param postgresVersion >}}-{{< param operatorVersion >}}`. | -| CollectSecretName | `create` | An optional attribute unless `crunchy-postgres-exporter` is specified in the `UserLabels`; contains the name of a Kubernetes Secret that contains the credentials for a PostgreSQL user that is used for metrics collection, and is created when the PostgreSQL cluster is first bootstrapped. For more information, please see `User Secret Specification`.| | ClusterName | `create` | The name of the PostgreSQL cluster, e.g. `hippo`. This is used to group PostgreSQL instances (primary, replicas) together. | | CustomConfig | `create` | If specified, references a custom ConfigMap to use when bootstrapping a PostgreSQL cluster. For the shape of this file, please see the section on [Custom Configuration]({{< relref "/advanced/custom-configuration.md" >}}) | | Database | `create` | The name of a database that the PostgreSQL user can log into after the PostgreSQL cluster is created. | | ExporterLimits | `create`, `update` | Specify the container resource limits that the `crunchy-postgres-exporter` sidecar uses when it is deployed with a PostgreSQL instance. Follows the [Kubernetes definitions of resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | -| ExporterPort | `create` | If the `"crunchy-postgres-exporter"` label is set in `UserLabels`, then this specifies the port that the metrics sidecar runs on (e.g. `9187`) | +| Exporter | `create`,`update` | If `true`, deploys the `crunchy-postgres-exporter` sidecar for metrics collection | +| ExporterPort | `create` | If `Exporter` is `true`, then this specifies the port that the metrics sidecar runs on (e.g. `9187`) | | ExporterResources | `create`, `update` | Specify the container resource requests that the `crunchy-postgres-exporter` sidecar uses when it is deployed with a PostgreSQL instance. Follows the [Kubernetes definitions of resource requests](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | | Limits | `create`, `update` | Specify the container resource limits that the PostgreSQL cluster should use. Follows the [Kubernetes definitions of resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | | Name | `create` | The name of the PostgreSQL instance that is the primary. On creation, this should be set to be the same as `ClusterName`. | diff --git a/docs/content/pgo-client/common-tasks.md b/docs/content/pgo-client/common-tasks.md index 8eefb412c2..58e7476dfa 100644 --- a/docs/content/pgo-client/common-tasks.md +++ b/docs/content/pgo-client/common-tasks.md @@ -128,7 +128,6 @@ Cluster: BackrestS3URIStyle: "" BackrestS3VerifyTLS: true DisableAutofail: false - PgmonitorPassword: "" EnableCrunchyadm: false DisableReplicaStartFailReinit: false PodAntiAffinity: preferred diff --git a/docs/content/pgo-client/reference/pgo_update_cluster.md b/docs/content/pgo-client/reference/pgo_update_cluster.md index 007c34b0fc..8a5af0e066 100644 --- a/docs/content/pgo-client/reference/pgo_update_cluster.md +++ b/docs/content/pgo-client/reference/pgo_update_cluster.md @@ -38,7 +38,9 @@ pgo update cluster [flags] --cpu string Set the number of millicores to request for the CPU, e.g. "100m" or "0.1". --cpu-limit string Set the number of millicores to limit for the CPU, e.g. "100m" or "0.1". --disable-autofail Disables autofail capabitilies in the cluster. + --disable-metrics Disable the metrics collection sidecar. May cause brief downtime. --enable-autofail Enables autofail capabitilies in the cluster. + --enable-metrics Enable the metrics collection sidecar. May cause brief downtime. --enable-standby Enables standby mode in the cluster(s) specified. --exporter-cpu string Set the number of millicores to request for CPU for the Crunchy Postgres Exporter sidecar container, e.g. "100m" or "0.1". --exporter-cpu-limit string Set the number of millicores to limit for CPU for the Crunchy Postgres Exporter sidecar container, e.g. "100m" or "0.1". @@ -70,7 +72,7 @@ pgo update cluster [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -84,4 +86,4 @@ pgo update cluster [flags] * [pgo update](/pgo-client/reference/pgo_update/) - Update a pgouser, pgorole, or cluster -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Dec-2020 diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/exporter.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/exporter.json index 9d430c3c20..3a3d5edddd 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/exporter.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/exporter.json @@ -35,7 +35,7 @@ "name": "EXPORTER_PG_USER", "valueFrom": { "secretKeyRef": { - "name": "{{.CollectSecretName}}", + "name": "{{.ExporterSecretName}}", "key": "username" } } @@ -44,7 +44,7 @@ "name": "EXPORTER_PG_PASSWORD", "valueFrom": { "secretKeyRef": { - "name": "{{.CollectSecretName}}", + "name": "{{.ExporterSecretName}}", "key": "password" } } diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index c411e8b498..d1a78cba41 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -960,9 +960,6 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. resp.Result.Users = append(resp.Result.Users, user) } - // there's a secret for the monitoring user too - newInstance.Spec.CollectSecretName = clusterName + crv1.ExporterSecretSuffix - // Create Backrest secret for S3/SSH Keys: // We make this regardless if backrest is enabled or not because // the deployment template always tries to mount /sshd volume @@ -1131,7 +1128,7 @@ func getClusterParams(request *msgs.CreateClusterRequest, name string, userLabel spec.CustomConfig = userLabelsMap[config.LABEL_CUSTOM_CONFIG] } - // enable the exporter sidecar based on the what the user based in or what + // enable the exporter sidecar based on the what the user passed in or what // the default value is. the user value takes precedence, unless it's false, // as the legacy check only looked for enablement spec.Exporter = request.MetricsFlag || apiserver.MetricsFlag @@ -1905,6 +1902,15 @@ func UpdateCluster(request *msgs.UpdateClusterRequest) msgs.UpdateClusterRespons cluster.ObjectMeta.Labels[config.LABEL_AUTOFAIL] = "false" } + // enable or disable the metrics collection sidecar + switch request.Metrics { + case msgs.UpdateClusterMetricsEnable: + cluster.Spec.Exporter = true + case msgs.UpdateClusterMetricsDisable: + cluster.Spec.Exporter = false + case msgs.UpdateClusterMetricsDoNothing: // this is never reached -- no-op + } + // enable or disable standby mode based on UpdateClusterStandbyStatus provided in // the request switch request.Standby { diff --git a/internal/apiserver/userservice/userimpl.go b/internal/apiserver/userservice/userimpl.go index 603fad07bd..39e0396184 100644 --- a/internal/apiserver/userservice/userimpl.go +++ b/internal/apiserver/userservice/userimpl.go @@ -558,7 +558,16 @@ func ShowUser(request *msgs.ShowUserRequest) msgs.ShowUserResponse { // // We ignore any errors...if the password get set, we add it. If not, we // don't - secretName := fmt.Sprintf(util.UserSecretFormat, result.ClusterName, result.Username) + secretName := "" + + // handle special cases with user names + secrets lining up + switch result.Username { + default: + secretName = fmt.Sprintf(util.UserSecretFormat, result.ClusterName, result.Username) + case "ccp_monitoring": + secretName = util.GenerateExporterSecretName(result.ClusterName) + } + password, _ := util.GetPasswordFromSecret(apiserver.Clientset, pod.Namespace, secretName) if password != "" { diff --git a/internal/config/pgoconfig.go b/internal/config/pgoconfig.go index 3fffa15a02..bf7c6fb35d 100644 --- a/internal/config/pgoconfig.go +++ b/internal/config/pgoconfig.go @@ -211,7 +211,6 @@ type ClusterStruct struct { BackrestS3URIStyle string BackrestS3VerifyTLS string DisableAutofail bool - PgmonitorPassword string EnableCrunchyadm bool DisableReplicaStartFailReinit bool PodAntiAffinity string diff --git a/internal/controller/pgcluster/pgclustercontroller.go b/internal/controller/pgcluster/pgclustercontroller.go index 701429e576..72ee582be3 100644 --- a/internal/controller/pgcluster/pgclustercontroller.go +++ b/internal/controller/pgcluster/pgclustercontroller.go @@ -237,6 +237,26 @@ func (c *Controller) onUpdate(oldObj, newObj interface{}) { } } + // see if we are adding / removing the metrics collection sidecar + if oldcluster.Spec.Exporter != newcluster.Spec.Exporter { + var err error + + // determine if the sidecar is being enabled/disabled and take the precursor + // actions before the deployment template is modified + switch newcluster.Spec.Exporter { + case true: + err = clusteroperator.AddExporter(c.Client, c.Client.Config, newcluster) + case false: + err = clusteroperator.RemoveExporter(c.Client, c.Client.Config, newcluster) + } + + if err == nil { + rollingUpdateFuncs = append(rollingUpdateFuncs, clusteroperator.UpdateExporterSidecar) + } else { + log.Errorf("could not update metrics collection sidecar: %q", err.Error()) + } + } + // see if any of the resource values have changed for the database or exporter container, // if so, update them if !reflect.DeepEqual(oldcluster.Spec.Resources, newcluster.Spec.Resources) || diff --git a/internal/operator/cluster/cluster.go b/internal/operator/cluster/cluster.go index 76eb3834c1..74840a8113 100644 --- a/internal/operator/cluster/cluster.go +++ b/internal/operator/cluster/cluster.go @@ -58,6 +58,9 @@ type ServiceTemplateFields struct { // ReplicaSuffix ... const ReplicaSuffix = "-replica" +// exporterContainerName is the name of the exporter container +const exporterContainerName = "exporter" + func AddClusterBase(clientset kubeapi.Interface, cl *crv1.Pgcluster, namespace string) { ctx := context.TODO() var err error diff --git a/internal/operator/cluster/clusterlogic.go b/internal/operator/cluster/clusterlogic.go index ded0c0e025..c464da161b 100644 --- a/internal/operator/cluster/clusterlogic.go +++ b/internal/operator/cluster/clusterlogic.go @@ -275,9 +275,17 @@ func getClusterDeploymentFields(clientset kubernetes.Interface, // 'crunchy-pgha-scope' label on the pgcluster cl.Spec.UserLabels[config.LABEL_PGHA_SCOPE] = cl.Spec.Name - // Set the exporter labels, if applicable + // If applicable, set the exporter labels, used for the scrapers, and create + // the secret. We don't need to take any additional actions, as the cluster + // creation process will handle those. Magic! if cl.Spec.Exporter { cl.Spec.UserLabels[config.LABEL_EXPORTER] = config.LABEL_TRUE + + log.Debugf("creating exporter secret for cluster %s", cl.Spec.Name) + + if _, err := CreateExporterSecret(clientset, cl); err != nil { + log.Error(err) + } } // set up a map of the names of the tablespaces as well as the storage classes @@ -314,7 +322,7 @@ func getClusterDeploymentFields(clientset kubernetes.Interface, PodAntiAffinity: operator.GetPodAntiAffinity(cl, crv1.PodAntiAffinityDeploymentDefault, cl.Spec.PodAntiAffinity.Default), ContainerResources: operator.GetResourcesJSON(cl.Spec.Resources, cl.Spec.Limits), ConfVolume: operator.GetConfVolume(clientset, cl, namespace), - ExporterAddon: operator.GetExporterAddon(clientset, namespace, &cl.Spec), + ExporterAddon: operator.GetExporterAddon(cl.Spec), BadgerAddon: operator.GetBadgerAddon(clientset, namespace, cl, cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY]), PgmonitorEnvVars: operator.GetPgmonitorEnvVars(cl), ScopeLabel: config.LABEL_PGHA_SCOPE, @@ -407,7 +415,6 @@ func scaleReplicaCreateDeployment(clientset kubernetes.Interface, var replicaDoc bytes.Buffer serviceName := replica.Spec.ClusterName + "-replica" - // replicaFlag := true // replicaLabels := operator.GetPrimaryLabels(serviceName, replica.Spec.ClusterName, replicaFlag, cluster.Spec.UserLabels) cluster.Spec.UserLabels[config.LABEL_REPLICA_NAME] = replica.Spec.Name @@ -472,7 +479,7 @@ func scaleReplicaCreateDeployment(clientset kubernetes.Interface, ContainerResources: operator.GetResourcesJSON(cluster.Spec.Resources, cluster.Spec.Limits), NodeSelector: operator.GetAffinity(replica.Spec.UserLabels["NodeLabelKey"], replica.Spec.UserLabels["NodeLabelValue"], "In"), PodAntiAffinity: operator.GetPodAntiAffinity(cluster, crv1.PodAntiAffinityDeploymentDefault, cluster.Spec.PodAntiAffinity.Default), - ExporterAddon: operator.GetExporterAddon(clientset, namespace, &cluster.Spec), + ExporterAddon: operator.GetExporterAddon(cluster.Spec), BadgerAddon: operator.GetBadgerAddon(clientset, namespace, cluster, replica.Spec.Name), ScopeLabel: config.LABEL_PGHA_SCOPE, PgbackrestEnvVars: operator.GetPgbackrestEnvVars(cluster, cluster.Labels[config.LABEL_BACKREST], replica.Spec.Name, diff --git a/internal/operator/cluster/common.go b/internal/operator/cluster/common.go new file mode 100644 index 0000000000..250662846f --- /dev/null +++ b/internal/operator/cluster/common.go @@ -0,0 +1,116 @@ +package cluster + +/* + Copyright 2020 Crunchy Data Solutions, Inc. + 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. +*/ + +import ( + "fmt" + "strings" + + "github.com/crunchydata/postgres-operator/internal/kubeapi" + "github.com/crunchydata/postgres-operator/internal/operator" + pgpassword "github.com/crunchydata/postgres-operator/internal/postgres/password" + "github.com/crunchydata/postgres-operator/internal/util" + crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" + log "github.com/sirupsen/logrus" + v1 "k8s.io/api/core/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" +) + +const ( + // disable the a Postgres user from logging in. This is safe from SQL + // injection as the string that is being interpolated is escaped + // + // This had the "PASSWORD NULL" feature, but this is only found in + // PostgreSQL 11+, and given we don't want to check for the PG version before + // running the command, we will not use it + sqlDisableLogin = `ALTER ROLE %s NOLOGIN;` + + // sqlEnableLogin is the SQL to update the password + // NOTE: this is safe from SQL injection as we explicitly add the inerpolated + // string as a MD5 hash and we are using the username. + // However, the escaping is handled in the util.SetPostgreSQLPassword function + sqlEnableLogin = `ALTER ROLE %s PASSWORD %s LOGIN;` +) + +// disablePostgresLogin disables the ability for a PostgreSQL user to log in +func disablePostgresLogin(clientset kubernetes.Interface, restconfig *rest.Config, cluster *crv1.Pgcluster, username string) error { + log.Debugf("disable user %q on cluster %q", username, cluster.Name) + // disable the pgbouncer user in the PostgreSQL cluster. + // first, get the primary pod. If we cannot do this, let's consider it an + // error and abort + pod, err := util.GetPrimaryPod(clientset, cluster) + if err != nil { + return err + } + + // This is safe from SQL injection as we are escaping the username + sql := strings.NewReader(fmt.Sprintf(sqlDisableLogin, util.SQLQuoteIdentifier(username))) + cmd := []string{"psql", "-p", cluster.Spec.Port} + + // exec into the pod to run the query + _, stderr, err := kubeapi.ExecToPodThroughAPI(restconfig, clientset, + cmd, "database", pod.Name, pod.ObjectMeta.Namespace, sql) + // if there is an error, log the error from the stderr and return the error + if err != nil { + return fmt.Errorf(stderr) + } + + return nil +} + +// generatePassword generates a password that is used for the PostgreSQL user +// system accounts. This goes off of the configured value for password length +func generatePassword() (string, error) { + // first, get the length of what the password should be + generatedPasswordLength := util.GeneratedPasswordLength(operator.Pgo.Cluster.PasswordLength) + // from there, the password can be generated! + return util.GeneratePassword(generatedPasswordLength) +} + +// makePostgresPassword creates the expected hash for a password type for a +// PostgreSQL password +func makePostgresPassword(passwordType pgpassword.PasswordType, username, password string) string { + // get the PostgreSQL password generate based on the password type + // as all of these values are valid, this not not error + postgresPassword, _ := pgpassword.NewPostgresPassword(passwordType, username, password) + + // create the PostgreSQL style hashed password and return + hashedPassword, _ := postgresPassword.Build() + + return hashedPassword +} + +// setPostgreSQLPassword updates the password of a user in a PostgreSQL +// cluster by executing into the Pod provided (i.e. a primary) and changing it +func setPostgreSQLPassword(clientset kubernetes.Interface, restconfig *rest.Config, pod *v1.Pod, port, + username, password string) error { + log.Debugf("set %q password in PostgreSQL", username) + + // we use the PostgreSQL "md5" hashing mechanism here to pre-hash the + // password. This is semi-hard coded but is now prepped for SCRAM as a + // password type can be passed in. Almost to SCRAM! + passwordHash := makePostgresPassword(pgpassword.MD5, username, password) + + if err := util.SetPostgreSQLPassword(clientset, restconfig, pod, + port, username, passwordHash, sqlEnableLogin); err != nil { + log.Error(err) + return err + } + + // and that's all! + return nil +} diff --git a/internal/operator/cluster/common_test.go b/internal/operator/cluster/common_test.go new file mode 100644 index 0000000000..24f0423969 --- /dev/null +++ b/internal/operator/cluster/common_test.go @@ -0,0 +1,39 @@ +package cluster + +/* + Copyright 2020 Crunchy Data Solutions, Inc. + 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. +*/ + +import ( + "testing" + + pgpassword "github.com/crunchydata/postgres-operator/internal/postgres/password" +) + +func TestMakePostgresPassword(t *testing.T) { + t.Run("md5", func(t *testing.T) { + t.Run("valid", func(t *testing.T) { + passwordType := pgpassword.MD5 + username := "pgbouncer" + password := "datalake" + expected := "md56294153764d389dc6830b6ce4f923cdb" + + actual := makePostgresPassword(passwordType, username, password) + + if actual != expected { + t.Errorf("expected: %q actual: %q", expected, actual) + } + }) + }) +} diff --git a/internal/operator/cluster/exporter.go b/internal/operator/cluster/exporter.go new file mode 100644 index 0000000000..957e47a7d1 --- /dev/null +++ b/internal/operator/cluster/exporter.go @@ -0,0 +1,331 @@ +package cluster + +/* + Copyright 2020 Crunchy Data Solutions, Inc. + 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. +*/ + +import ( + "context" + "encoding/json" + "fmt" + "strconv" + + "github.com/crunchydata/postgres-operator/internal/config" + "github.com/crunchydata/postgres-operator/internal/kubeapi" + "github.com/crunchydata/postgres-operator/internal/operator" + "github.com/crunchydata/postgres-operator/internal/util" + crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" + + log "github.com/sirupsen/logrus" + appsv1 "k8s.io/api/apps/v1" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" +) + +const ( + // exporterInstallScript references the embedded script that installs all of + // the pgMonitor functions + exporterInstallScript = "/opt/crunchy/bin/exporter/install.sh" + + // exporterServicePortName is the name used to identify the exporter port in + // the service + exporterServicePortName = "postgres-exporter" +) + +// AddExporter ensures that a PostgreSQL cluster is able to undertake the +// actions required by the "crunchy-postgres-exporter", i.e. +// +// - enable a service port so scrapers can access the metrics +// - it can authenticate as the "ccp_monitoring" user; manages the Secret as +// well +// - all of the monitoring views and functions are available +func AddExporter(clientset kubernetes.Interface, restconfig *rest.Config, cluster *crv1.Pgcluster) error { + ctx := context.TODO() + + // even if this is a standby, we can still set up a Secret (though the password + // value of the Secret is of limited use when the standby is promoted, it can + // be rotated, similar to the pgBouncer password) + + // only create a password Secret if one does not already exist, which is + // handled in the delegated function + password, err := CreateExporterSecret(clientset, cluster) + if err != nil { + return err + } + + // set up the Service, which is still needed on a standby + svc, err := clientset.CoreV1().Services(cluster.Namespace).Get(ctx, cluster.Name, metav1.GetOptions{}) + if err != nil { + return err + } + + // loop over the service ports to see if exporter port is already set up. if + // it is, we can return from there + for _, svcPort := range svc.Spec.Ports { + if svcPort.Name == exporterServicePortName { + return nil + } + } + + // otherwise, we need to append a service port to the list + port, err := strconv.ParseInt( + util.GetValueOrDefault(cluster.Spec.ExporterPort, operator.Pgo.Cluster.ExporterPort), 10, 32) + if err != nil { + return err + } + + svcPort := v1.ServicePort{ + Name: exporterServicePortName, + Protocol: v1.ProtocolTCP, + Port: int32(port), + } + + svc.Spec.Ports = append(svc.Spec.Ports, svcPort) + + if _, err := clientset.CoreV1().Services(svc.Namespace).Update(ctx, svc, metav1.UpdateOptions{}); err != nil { + return err + } + + // after the secret if this is a standby, exit early + // this can't be installed if this is a standby, so abort if that's the case + if cluster.Spec.Standby { + return ErrStandbyNotAllowed + } + + // get the primary pod, which is needed to update the password for the + // exporter user + pod, err := util.GetPrimaryPod(clientset, cluster) + if err != nil { + return err + } + + // add the m onitoring user and all the views associated with this + // user. this can be done by executing a script on the container itself + cmd := []string{"/bin/bash", exporterInstallScript} + + if _, stderr, err := kubeapi.ExecToPodThroughAPI(restconfig, clientset, + cmd, "database", pod.Name, pod.ObjectMeta.Namespace, nil); err != nil { + return fmt.Errorf(stderr) + } + + // attempt to update the password in PostgreSQL, as this is how the exporter + // will properly interface with PostgreSQL + return setPostgreSQLPassword(clientset, restconfig, pod, cluster.Spec.Port, crv1.PGUserMonitor, password) +} + +// CreateExporterSecret create a secret used by the exporter containing the +// user credientials. Sees if a Secret already exists and if it does, uses that. +// Otherwise, it will generate the password. Returns an error if it fails. +func CreateExporterSecret(clientset kubernetes.Interface, cluster *crv1.Pgcluster) (string, error) { + ctx := context.TODO() + secretName := util.GenerateExporterSecretName(cluster.Name) + + // see if this secret already exists...if it does, then take an early exit + if password, err := util.GetPasswordFromSecret(clientset, cluster.Namespace, secretName); err == nil { + log.Infof("exporter secret %s already present, will reuse", secretName) + return password, nil + } + + // well, we have to generate the password + password, err := generatePassword() + if err != nil { + return "", err + } + + // the remainder of this is generating the various entries in the pgbouncer + // secret, i.e. substituting values into templates files that contain: + // - the pgbouncer user password + // - the pgbouncer "users.txt" file that contains the credentials for the + // "pgbouncer" user + + // now, we can do what we came here to do, which is create the secret + secret := v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Labels: map[string]string{ + config.LABEL_EXPORTER: config.LABEL_TRUE, + config.LABEL_PG_CLUSTER: cluster.Name, + config.LABEL_VENDOR: config.LABEL_CRUNCHY, + }, + }, + Data: map[string][]byte{ + "username": []byte(crv1.PGUserMonitor), + "password": []byte(password), + }, + } + + if _, err := clientset.CoreV1().Secrets(cluster.Namespace). + Create(ctx, &secret, metav1.CreateOptions{}); err != nil { + log.Error(err) + return "", err + } + + return password, nil +} + +// RemoveExporter disables the ability for a PostgreSQL cluster to use the +// exporter functionality. In particular this function: +// +// - disallows the login of the monitoring user (ccp_monitoring) +// - removes the Secret that contains the ccp_monitoring user credentials +// - removes the port on the cluster Service +// +// This does not modify the Deployment that has the exporter sidecar. That is +// handled by the "UpdateExporter" function, so it can be handled as part of a +// rolling update +func RemoveExporter(clientset kubernetes.Interface, restconfig *rest.Config, cluster *crv1.Pgcluster) error { + ctx := context.TODO() + + // close the service port + svc, err := clientset.CoreV1().Services(cluster.Namespace).Get(ctx, cluster.Name, metav1.GetOptions{}) + if err != nil { + return err + } + + svcPorts := []v1.ServicePort{} + + for _, svcPort := range svc.Spec.Ports { + // if we find the service port for the exporter, skip it in the loop + if svcPort.Name == exporterServicePortName { + continue + } + + svcPorts = append(svcPorts, svcPort) + } + + svc.Spec.Ports = svcPorts + + if _, err := clientset.CoreV1().Services(svc.Namespace).Update(ctx, svc, metav1.UpdateOptions{}); err != nil { + return err + } + + // disable the user before clearing the Secret, so there does not end up being + // a race condition between the existence of the Secret and the Pod definition + // if this is a standby cluster, return as we cannot execute any SQL + if !cluster.Spec.Standby { + // if this fails, warn and continue + if err := disablePostgresLogin(clientset, restconfig, cluster, crv1.PGUserMonitor); err != nil { + log.Warn(err) + } + } + + // delete the Secret. If there is an error deleting the Secret, log as info + // and continue on + if err := clientset.CoreV1().Secrets(cluster.Namespace).Delete(ctx, + util.GenerateExporterSecretName(cluster.Name), metav1.DeleteOptions{}); err != nil { + log.Warnf("could not remove exporter secret: %q", err.Error()) + } + + return nil +} + +// UpdateExporterSidecar either adds or emoves the metrics sidcar from the +// cluster. This is meant to be used as a rolling update callback function +func UpdateExporterSidecar(cluster *crv1.Pgcluster, deployment *appsv1.Deployment) error { + // need to determine if we are adding or removing + if cluster.Spec.Exporter { + return addExporterSidecar(cluster, deployment) + } + + removeExporterSidecar(deployment) + + return nil +} + +// addExporterSidecar adds the metrics collection exporter to a Deployment +// This does two things: +// - adds the exporter container to the manifest. If the exporter manifest +// already exists, this supersedes it. +// - adds the exporter label to the label template, so it can be discovered that +// this container has an exporter +func addExporterSidecar(cluster *crv1.Pgcluster, deployment *appsv1.Deployment) error { + // use the legacy template generation to make the appropriate substitutions, + // and then get said generation to be placed into an actual Container object + template := operator.GetExporterAddon(cluster.Spec) + + container := v1.Container{} + + if err := json.Unmarshal([]byte(template), &container); err != nil { + return fmt.Errorf("error unmarshalling exporter json into Container: %w ", err) + } + + // append the container to the deployment container list. However, we are + // going to do this carefully, in case the exporter container already exists. + // this definition will supersede any exporter container already in the + // containers list + containers := []v1.Container{} + for _, c := range deployment.Spec.Template.Spec.Containers { + // skip if this is the exporter container + if c.Name == exporterContainerName { + continue + } + + containers = append(containers, c) + } + + // add the exporter container and override the containers list definition + containers = append(containers, container) + deployment.Spec.Template.Spec.Containers = containers + + // add the label to the deployment template + deployment.Spec.Template.ObjectMeta.Labels[config.LABEL_EXPORTER] = config.LABEL_TRUE + + return nil +} + +// removeExporterSidecar removes the metrics collection exporter to a +// Deployment. +// +// This involves: +// - Removing the container entry for the exporter +// - Removing the label from the deployment template +func removeExporterSidecar(deployment *appsv1.Deployment) { + // first, find the container entry in the list of containers and remove it + containers := []v1.Container{} + for _, c := range deployment.Spec.Template.Spec.Containers { + // skip if this is the exporter container + if c.Name == exporterContainerName { + continue + } + + containers = append(containers, c) + } + + deployment.Spec.Template.Spec.Containers = containers + + // alright, so this moves towards the mix of modern/legacy behavior, but we + // need to scan the environmental variables on the "database" container and + // remove one with the name "PGMONITOR_PASSWORD" + for i, c := range deployment.Spec.Template.Spec.Containers { + if c.Name == "database" { + env := []v1.EnvVar{} + + for _, e := range c.Env { + if e.Name == "PGMONITOR_PASSWORD" { + continue + } + + env = append(env, e) + } + + deployment.Spec.Template.Spec.Containers[i].Env = env + break + } + } + + // finally, remove the label + delete(deployment.Spec.Template.ObjectMeta.Labels, config.LABEL_EXPORTER) +} diff --git a/internal/operator/cluster/pgbouncer.go b/internal/operator/cluster/pgbouncer.go index cf8dcaf7c0..a9a6d5ae2a 100644 --- a/internal/operator/cluster/pgbouncer.go +++ b/internal/operator/cluster/pgbouncer.go @@ -99,20 +99,6 @@ const ( // PostgreSQL cluster sqlCheckPgBouncerInstall = `SELECT EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = 'pgbouncer' LIMIT 1);` - // disable the pgbouncer user from logging in. This is safe from SQL injection - // as the string that is being interpolated is the util.PgBouncerUser constant - // - // This had the "PASSWORD NULL" feature, but this is only found in - // PostgreSQL 11+, and given we don't want to check for the PG version before - // running the command, we will not use it - sqlDisableLogin = `ALTER ROLE "%s" NOLOGIN;` - - // sqlEnableLogin is the SQL to update the password - // NOTE: this is safe from SQL injection as we explicitly add the inerpolated - // string as a MD5 hash and we are using the crv1.PGUserPgBouncer constant - // However, the escaping is handled in the util.SetPostgreSQLPassword function - sqlEnableLogin = `ALTER ROLE %s PASSWORD %s LOGIN;` - // sqlGetDatabasesForPgBouncer gets all the databases where pgBouncer can be // installed or uninstalled sqlGetDatabasesForPgBouncer = `SELECT datname FROM pg_catalog.pg_database WHERE datname NOT IN ('template0') AND datallowconn;` @@ -180,7 +166,7 @@ func AddPgbouncer(clientset kubernetes.Interface, restconfig *rest.Config, clust // attempt to update the password in PostgreSQL, as this is how pgBouncer // will properly interface with PostgreSQL - if err := setPostgreSQLPassword(clientset, restconfig, pod, cluster.Spec.Port, pgBouncerPassword); err != nil { + if err := setPostgreSQLPassword(clientset, restconfig, pod, cluster.Spec.Port, crv1.PGUserPgBouncer, pgBouncerPassword); err != nil { return err } } @@ -317,7 +303,7 @@ func RotatePgBouncerPassword(clientset kubernetes.Interface, restconfig *rest.Co // next, update the PostgreSQL primary with the new password. If this fails // we definitely return an error - if err := setPostgreSQLPassword(clientset, restconfig, primaryPod, cluster.Spec.Port, password); err != nil { + if err := setPostgreSQLPassword(clientset, restconfig, primaryPod, cluster.Spec.Port, crv1.PGUserPgBouncer, password); err != nil { return err } @@ -326,7 +312,7 @@ func RotatePgBouncerPassword(clientset kubernetes.Interface, restconfig *rest.Co // PostgreSQL to perform its authentication secret.Data["password"] = []byte(password) secret.Data["users.txt"] = util.GeneratePgBouncerUsersFileBytes( - makePostgresPassword(pgpassword.MD5, password)) + makePostgresPassword(pgpassword.MD5, crv1.PGUserPgBouncer, password)) // update the secret if _, err := clientset.CoreV1().Secrets(cluster.Namespace). @@ -658,7 +644,7 @@ func createPgbouncerSecret(clientset kubernetes.Interface, cluster *crv1.Pgclust Data: map[string][]byte{ "password": []byte(password), "users.txt": util.GeneratePgBouncerUsersFileBytes( - makePostgresPassword(pgpassword.MD5, password)), + makePostgresPassword(pgpassword.MD5, crv1.PGUserPgBouncer, password)), }, } @@ -698,32 +684,7 @@ func createPgBouncerService(clientset kubernetes.Interface, cluster *crv1.Pgclus // disable the "pgbouncer" role from being able to log in. It keeps the // artificats that were created during normal pgBouncer operation func disablePgBouncer(clientset kubernetes.Interface, restconfig *rest.Config, cluster *crv1.Pgcluster) error { - log.Debugf("disable pgbouncer user on cluster [%s]", cluster.Name) - // disable the pgbouncer user in the PostgreSQL cluster. - // first, get the primary pod. If we cannot do this, let's consider it an - // error and abort - pod, err := util.GetPrimaryPod(clientset, cluster) - - if err != nil { - return err - } - - // This is safe from SQL injection as we are using constants and a well defined - // string - sql := strings.NewReader(fmt.Sprintf(sqlDisableLogin, crv1.PGUserPgBouncer)) - cmd := []string{"psql"} - - // exec into the pod to run the query - _, stderr, err := kubeapi.ExecToPodThroughAPI(restconfig, clientset, - cmd, "database", pod.Name, pod.ObjectMeta.Namespace, sql) - - // if there is an error, log the error from the stderr and return the error - if err != nil { - log.Error(stderr) - return err - } - - return nil + return disablePostgresLogin(clientset, restconfig, cluster, crv1.PGUserPgBouncer) } // execPgBouncerScript runs a script pertaining to the management of pgBouncer @@ -744,15 +705,6 @@ func execPgBouncerScript(clientset kubernetes.Interface, restconfig *rest.Config } } -// generatePassword generates a password that is used for the "pgbouncer" -// PostgreSQL user that provides the associated pgBouncer functionality -func generatePassword() (string, error) { - // first, get the length of what the password should be - generatedPasswordLength := util.GeneratedPasswordLength(operator.Pgo.Cluster.PasswordLength) - // from there, the password can be generated! - return util.GeneratePassword(generatedPasswordLength) -} - // generatePgBouncerConf generates the content that is stored in the secret // for the "pgbouncer.ini" file func generatePgBouncerConf(cluster *crv1.Pgcluster) (string, error) { @@ -879,19 +831,6 @@ func isPgBouncerTLSEnabled(cluster *crv1.Pgcluster) bool { return cluster.Spec.PgBouncer.TLSSecret != "" && cluster.Spec.TLS.IsTLSEnabled() } -// makePostgresPassword creates the expected hash for a password type for a -// PostgreSQL password -func makePostgresPassword(passwordType pgpassword.PasswordType, password string) string { - // get the PostgreSQL password generate based on the password type - // as all of these values are valid, this not not error - postgresPassword, _ := pgpassword.NewPostgresPassword(passwordType, crv1.PGUserPgBouncer, password) - - // create the PostgreSQL style hashed password and return - hashedPassword, _ := postgresPassword.Build() - - return hashedPassword -} - // publishPgBouncerEvent publishes one of the events on the event stream func publishPgBouncerEvent(eventType string, cluster *crv1.Pgcluster) { var event events.EventInterface @@ -932,26 +871,6 @@ func publishPgBouncerEvent(eventType string, cluster *crv1.Pgcluster) { } } -// setPostgreSQLPassword updates the pgBouncer password in the PostgreSQL -// cluster by executing into the primary Pod and changing it -func setPostgreSQLPassword(clientset kubernetes.Interface, restconfig *rest.Config, pod *v1.Pod, port, password string) error { - log.Debug("set pgbouncer password in PostgreSQL") - - // we use the PostgreSQL "md5" hashing mechanism here to pre-hash the - // password. This is semi-hard coded but is now prepped for SCRAM as a - // password type can be passed in. Almost to SCRAM! - sqlpgBouncerPassword := makePostgresPassword(pgpassword.MD5, password) - - if err := util.SetPostgreSQLPassword(clientset, restconfig, pod, - port, crv1.PGUserPgBouncer, sqlpgBouncerPassword, sqlEnableLogin); err != nil { - log.Error(err) - return err - } - - // and that's all! - return nil -} - // updatePgBouncerReplicas updates the pgBouncer Deployment with the number // of replicas (Pods) that it should run. Presently, this is fairly naive, but // as pgBouncer is "semi-stateful" we may want to improve upon this in the diff --git a/internal/operator/cluster/pgbouncer_test.go b/internal/operator/cluster/pgbouncer_test.go index b95d96828c..0784afa58c 100644 --- a/internal/operator/cluster/pgbouncer_test.go +++ b/internal/operator/cluster/pgbouncer_test.go @@ -18,7 +18,6 @@ package cluster import ( "testing" - pgpassword "github.com/crunchydata/postgres-operator/internal/postgres/password" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" ) @@ -72,21 +71,3 @@ func TestIsPgBouncerTLSEnabled(t *testing.T) { }) }) } - -func TestMakePostgresPassword(t *testing.T) { - - t.Run("md5", func(t *testing.T) { - t.Run("valid", func(t *testing.T) { - passwordType := pgpassword.MD5 - password := "datalake" - expected := "md56294153764d389dc6830b6ce4f923cdb" - - actual := makePostgresPassword(passwordType, password) - - if actual != expected { - t.Errorf("expected: %q actual: %q", expected, actual) - } - }) - - }) -} diff --git a/internal/operator/clusterutilities.go b/internal/operator/clusterutilities.go index 2fb2277330..309f6d5689 100644 --- a/internal/operator/clusterutilities.go +++ b/internal/operator/clusterutilities.go @@ -94,7 +94,7 @@ type exporterTemplateFields struct { PGOImagePrefix string PgPort string ExporterPort string - CollectSecretName string + ExporterSecretName string ContainerResources string TLSOnly bool } @@ -357,46 +357,45 @@ func GetBadgerAddon(clientset kubernetes.Interface, namespace string, cluster *c return "" } -func GetExporterAddon(clientset kubernetes.Interface, namespace string, spec *crv1.PgclusterSpec) string { - - if spec.Exporter { - log.Debug("crunchy-postgres-exporter was found as a label on cluster create") - - log.Debugf("creating exporter secret for cluster %s", spec.Name) - err := util.CreateSecret(clientset, spec.Name, spec.CollectSecretName, crv1.PGUserMonitor, - Pgo.Cluster.PgmonitorPassword, namespace) - if err != nil { - log.Error(err) - } +// GetExporterAddon returns the template used to create an exporter container +// for metrics. This is semi-legacy, but updated to match the current way of +// handling this +func GetExporterAddon(spec crv1.PgclusterSpec) string { + // do not execute if metrics are not enabled + if !spec.Exporter { + return "" + } - exporterTemplateFields := exporterTemplateFields{} - exporterTemplateFields.Name = spec.Name - exporterTemplateFields.JobName = spec.Name - exporterTemplateFields.PGOImageTag = Pgo.Pgo.PGOImageTag - exporterTemplateFields.ExporterPort = spec.ExporterPort - exporterTemplateFields.PGOImagePrefix = util.GetValueOrDefault(spec.PGOImagePrefix, Pgo.Pgo.PGOImagePrefix) - exporterTemplateFields.PgPort = spec.Port - exporterTemplateFields.CollectSecretName = spec.CollectSecretName - exporterTemplateFields.ContainerResources = GetResourcesJSON(spec.ExporterResources, spec.ExporterLimits) + log.Debug("crunchy-postgres-exporter was found as a label on cluster create") + + exporterTemplateFields := exporterTemplateFields{ + ContainerResources: GetResourcesJSON(spec.ExporterResources, spec.ExporterLimits), + ExporterPort: spec.ExporterPort, + ExporterSecretName: util.GenerateExporterSecretName(spec.ClusterName), + JobName: spec.Name, + Name: spec.Name, + PGOImagePrefix: util.GetValueOrDefault(spec.PGOImagePrefix, Pgo.Pgo.PGOImagePrefix), + PGOImageTag: Pgo.Pgo.PGOImageTag, + PgPort: spec.Port, // see if TLS only is set. however, this also requires checking to see if // TLS is enabled in this case. The reason is that even if TLS is only just // enabled, because the connection is over an internal interface, we do not // need to have the overhead of a TLS connection - exporterTemplateFields.TLSOnly = spec.TLS.IsTLSEnabled() && spec.TLSOnly + TLSOnly: (spec.TLS.IsTLSEnabled() && spec.TLSOnly), + } - var exporterDoc bytes.Buffer - err = config.ExporterTemplate.Execute(&exporterDoc, exporterTemplateFields) - if err != nil { - log.Error(err.Error()) - return "" - } + if CRUNCHY_DEBUG { + _ = config.ExporterTemplate.Execute(os.Stdout, exporterTemplateFields) + } - if CRUNCHY_DEBUG { - config.ExporterTemplate.Execute(os.Stdout, exporterTemplateFields) - } - return exporterDoc.String() + exporterDoc := bytes.Buffer{} + + if err := config.ExporterTemplate.Execute(&exporterDoc, exporterTemplateFields); err != nil { + log.Error(err) + return "" } - return "" + + return exporterDoc.String() } //consolidate with cluster.GetConfVolume @@ -775,7 +774,7 @@ func GetPgmonitorEnvVars(cluster *crv1.Pgcluster) string { } fields := PgmonitorEnvVarsTemplateFields{ - ExporterSecret: cluster.Spec.CollectSecretName, + ExporterSecret: util.GenerateExporterSecretName(cluster.Name), } doc := bytes.Buffer{} diff --git a/internal/operator/common.go b/internal/operator/common.go index faeb64fcba..a3917dfc27 100644 --- a/internal/operator/common.go +++ b/internal/operator/common.go @@ -124,11 +124,6 @@ func Initialize(clientset kubernetes.Interface) { log.Debugf("PGOImagePrefix set, using %s", Pgo.Pgo.PGOImagePrefix) } - if Pgo.Cluster.PgmonitorPassword == "" { - log.Debug("pgo.yaml PgmonitorPassword not set, using default") - Pgo.Cluster.PgmonitorPassword = "password" - } - // In a RELATED_IMAGE_* world, this does not _need_ to be set, but our // installer does set it up so we could be ok... if Pgo.Pgo.PGOImageTag == "" { diff --git a/internal/util/exporter.go b/internal/util/exporter.go new file mode 100644 index 0000000000..d46ad8cf53 --- /dev/null +++ b/internal/util/exporter.go @@ -0,0 +1,29 @@ +package util + +/* + Copyright 2020 Crunchy Data Solutions, Inc. + 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. +*/ + +import "fmt" + +// exporterSecretFormat is the format of the name of the exporter secret, i.e. +// "-exporter-secret" +// #nosec G101 +const exporterSecretFormat = "%s-exporter-secret" + +// GenerateExporterSecretName returns the name of the secret that contains +// information around a monitoring user +func GenerateExporterSecretName(clusterName string) string { + return fmt.Sprintf(exporterSecretFormat, clusterName) +} diff --git a/internal/util/exporter_test.go b/internal/util/exporter_test.go new file mode 100644 index 0000000000..ffbde3a6e1 --- /dev/null +++ b/internal/util/exporter_test.go @@ -0,0 +1,32 @@ +package util + +/* + Copyright 2020 Crunchy Data Solutions, Inc. + 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. +*/ + +import ( + "testing" +) + +func TestGenerateExporterSecretName(t *testing.T) { + t.Run("success", func(t *testing.T) { + clusterName := "hippo" + expected := clusterName + "-exporter-secret" + actual := GenerateExporterSecretName(clusterName) + + if expected != actual { + t.Fatalf("expected %q actual %q", expected, actual) + } + }) +} diff --git a/pkg/apis/crunchydata.com/v1/cluster.go b/pkg/apis/crunchydata.com/v1/cluster.go index 63d3914f0f..fb121ea835 100644 --- a/pkg/apis/crunchydata.com/v1/cluster.go +++ b/pkg/apis/crunchydata.com/v1/cluster.go @@ -111,7 +111,6 @@ type PgclusterSpec struct { UserSecretName string `json:"usersecretname"` RootSecretName string `json:"rootsecretname"` PrimarySecretName string `json:"primarysecretname"` - CollectSecretName string `json:"collectSecretName"` Status string `json:"status"` CustomConfig string `json:"customconfig"` UserLabels map[string]string `json:"userlabels"` diff --git a/pkg/apis/crunchydata.com/v1/common.go b/pkg/apis/crunchydata.com/v1/common.go index 723c2a0a60..242025f038 100644 --- a/pkg/apis/crunchydata.com/v1/common.go +++ b/pkg/apis/crunchydata.com/v1/common.go @@ -31,9 +31,6 @@ const UserSecretSuffix = "-secret" // PrimarySecretSuffix ... const PrimarySecretSuffix = "-primaryuser-secret" -// ExporterSecretSuffix ... -const ExporterSecretSuffix = "-exporter-secret" - // StorageExisting ... const StorageExisting = "existing" diff --git a/pkg/apiservermsgs/clustermsgs.go b/pkg/apiservermsgs/clustermsgs.go index 69bbdafe49..c83ffb6e25 100644 --- a/pkg/apiservermsgs/clustermsgs.go +++ b/pkg/apiservermsgs/clustermsgs.go @@ -356,6 +356,16 @@ const ( UpdateClusterAutofailDisable ) +// UpdateClusterMetrics determines whether or not to enable/disable the metrics +// collection sidecar in a cluster +type UpdateClusterMetrics int + +const ( + UpdateClusterMetricsDoNothing UpdateClusterMetrics = iota + UpdateClusterMetricsEnable + UpdateClusterMetricsDisable +) + // UpdateClusterStandbyStatus defines the types for updating the Standby status type UpdateClusterStandbyStatus int @@ -426,10 +436,13 @@ type UpdateClusterRequest struct { // MemoryRequest is the value of how much RAM should be requested for // deploying the PostgreSQL cluster MemoryRequest string - Standby UpdateClusterStandbyStatus - Startup bool - Shutdown bool - Tablespaces []ClusterTablespaceDetail + // Metrics allows for the enabling/disabling of the metrics sidecar. This can + // cause downtime and triggers a rolling update + Metrics UpdateClusterMetrics + Standby UpdateClusterStandbyStatus + Startup bool + Shutdown bool + Tablespaces []ClusterTablespaceDetail } // UpdateClusterResponse ... From 021effba8eb56eeb06f75fe7b43db7a91404ceb1 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Wed, 16 Dec 2020 18:37:28 -0500 Subject: [PATCH 049/276] Allow for the metrics agent password to be rotated This introduces the `--exporter-rotate-password` flag to `pgo update cluster` so that the metrics collection password can be rotated. --- cmd/pgo/cmd/cluster.go | 1 + cmd/pgo/cmd/update.go | 4 ++ .../reference/pgo_update_cluster.md | 3 +- .../apiserver/clusterservice/clusterimpl.go | 13 +++- .../pgcluster/pgclustercontroller.go | 5 +- internal/controller/pod/promotionhandler.go | 8 +++ internal/operator/cluster/common.go | 34 +++++++++-- internal/operator/cluster/common_test.go | 2 +- internal/operator/cluster/exporter.go | 35 ++++++++++- internal/operator/cluster/pgbouncer.go | 59 ++++--------------- internal/operator/clusterutilities.go | 2 - pkg/apiservermsgs/clustermsgs.go | 3 + 12 files changed, 108 insertions(+), 61 deletions(-) diff --git a/cmd/pgo/cmd/cluster.go b/cmd/pgo/cmd/cluster.go index b4bf417697..5c8a318fc3 100644 --- a/cmd/pgo/cmd/cluster.go +++ b/cmd/pgo/cmd/cluster.go @@ -597,6 +597,7 @@ func updateCluster(args []string, ns string) { r.ExporterCPULimit = ExporterCPULimit r.ExporterMemoryRequest = ExporterMemoryRequest r.ExporterMemoryLimit = ExporterMemoryLimit + r.ExporterRotatePassword = ExporterRotatePassword r.Clustername = args r.Startup = Startup r.Shutdown = Shutdown diff --git a/cmd/pgo/cmd/update.go b/cmd/pgo/cmd/update.go index 303f64b74b..140c06cecd 100644 --- a/cmd/pgo/cmd/update.go +++ b/cmd/pgo/cmd/update.go @@ -38,6 +38,9 @@ var ( EnableMetrics bool // ExpireUser sets a user to having their password expired ExpireUser bool + // ExporterRotatePassword rotates the password for the designed PostgreSQL + // user for handling metrics scraping + ExporterRotatePassword bool // PgoroleChangePermissions does something with the pgouser access controls, // I'm not sure but I wanted this at least to be documented PgoroleChangePermissions bool @@ -115,6 +118,7 @@ func init() { "the Crunchy Postgres Exporter sidecar container.") UpdateClusterCmd.Flags().BoolVar(&EnableMetrics, "enable-metrics", false, "Enable the metrics collection sidecar. May cause brief downtime.") + UpdateClusterCmd.Flags().BoolVar(&ExporterRotatePassword, "exporter-rotate-password", false, "Used to rotate the password for the metrics collection agent.") UpdateClusterCmd.Flags().BoolVarP(&EnableStandby, "enable-standby", "", false, "Enables standby mode in the cluster(s) specified.") UpdateClusterCmd.Flags().BoolVar(&Startup, "startup", false, "Restart the database cluster if it "+ diff --git a/docs/content/pgo-client/reference/pgo_update_cluster.md b/docs/content/pgo-client/reference/pgo_update_cluster.md index 8a5af0e066..e480bf767f 100644 --- a/docs/content/pgo-client/reference/pgo_update_cluster.md +++ b/docs/content/pgo-client/reference/pgo_update_cluster.md @@ -46,6 +46,7 @@ pgo update cluster [flags] --exporter-cpu-limit string Set the number of millicores to limit for CPU for the Crunchy Postgres Exporter sidecar container, e.g. "100m" or "0.1". --exporter-memory string Set the amount of memory to request for the Crunchy Postgres Exporter sidecar container. --exporter-memory-limit string Set the amount of memory to limit for the Crunchy Postgres Exporter sidecar container. + --exporter-rotate-password Used to rotate the password for the metrics collection agent. -h, --help help for cluster --memory string Set the amount of RAM to request, e.g. 1GiB. --memory-limit string Set the amount of RAM to limit, e.g. 1GiB. @@ -86,4 +87,4 @@ pgo update cluster [flags] * [pgo update](/pgo-client/reference/pgo_update/) - Update a pgouser, pgorole, or cluster -###### Auto generated by spf13/cobra on 14-Dec-2020 +###### Auto generated by spf13/cobra on 16-Dec-2020 diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index d1a78cba41..d87c01da39 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -29,6 +29,7 @@ import ( "github.com/crunchydata/postgres-operator/internal/config" "github.com/crunchydata/postgres-operator/internal/kubeapi" "github.com/crunchydata/postgres-operator/internal/operator/backrest" + clusteroperator "github.com/crunchydata/postgres-operator/internal/operator/cluster" "github.com/crunchydata/postgres-operator/internal/util" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" @@ -1891,7 +1892,7 @@ func UpdateCluster(request *msgs.UpdateClusterRequest) msgs.UpdateClusterRespons return response } - for _, cluster := range clusterList.Items { + for i, cluster := range clusterList.Items { //set autofail=true or false on each pgcluster CRD // Make the change based on the value of Autofail vis-a-vis UpdateClusterAutofailStatus @@ -2026,6 +2027,16 @@ func UpdateCluster(request *msgs.UpdateClusterRequest) msgs.UpdateClusterRespons cluster.Spec.ExporterResources[v1.ResourceMemory] = quantity } + // an odd one...if rotating the password is requested, we can perform this + // as an operational action and handle it here. + // if it fails...just put a in the logs. + if cluster.Spec.Exporter && request.ExporterRotatePassword { + if err := clusteroperator.RotateExporterPassword(apiserver.Clientset, apiserver.RESTConfig, + &clusterList.Items[i]); err != nil { + log.Error(err) + } + } + // set any user-defined annotations // go through each annotation grouping and make the appropriate changes in the // equivalent cluster annotation group diff --git a/internal/controller/pgcluster/pgclustercontroller.go b/internal/controller/pgcluster/pgclustercontroller.go index 72ee582be3..24a6b78a6b 100644 --- a/internal/controller/pgcluster/pgclustercontroller.go +++ b/internal/controller/pgcluster/pgclustercontroller.go @@ -243,10 +243,9 @@ func (c *Controller) onUpdate(oldObj, newObj interface{}) { // determine if the sidecar is being enabled/disabled and take the precursor // actions before the deployment template is modified - switch newcluster.Spec.Exporter { - case true: + if newcluster.Spec.Exporter { err = clusteroperator.AddExporter(c.Client, c.Client.Config, newcluster) - case false: + } else { err = clusteroperator.RemoveExporter(c.Client, c.Client.Config, newcluster) } diff --git a/internal/controller/pod/promotionhandler.go b/internal/controller/pod/promotionhandler.go index 123f422d46..2585f50653 100644 --- a/internal/controller/pod/promotionhandler.go +++ b/internal/controller/pod/promotionhandler.go @@ -106,6 +106,14 @@ func (c *Controller) handleStandbyPromotion(newPod *apiv1.Pod, cluster crv1.Pgcl return err } + // rotate the exporter password if the metrics sidecar is enabled + if cluster.Spec.Exporter { + if err := clusteroperator.RotateExporterPassword(c.Client, c.Client.Config, &cluster); err != nil { + log.Error(err) + return err + } + } + // rotate the pgBouncer passwords if pgbouncer is enabled within the cluster if cluster.Spec.PgBouncer.Enabled() { if err := clusteroperator.RotatePgBouncerPassword(c.Client, c.Client.Config, &cluster); err != nil { diff --git a/internal/operator/cluster/common.go b/internal/operator/cluster/common.go index 250662846f..ebdc5adfec 100644 --- a/internal/operator/cluster/common.go +++ b/internal/operator/cluster/common.go @@ -46,8 +46,8 @@ const ( sqlEnableLogin = `ALTER ROLE %s PASSWORD %s LOGIN;` ) -// disablePostgresLogin disables the ability for a PostgreSQL user to log in -func disablePostgresLogin(clientset kubernetes.Interface, restconfig *rest.Config, cluster *crv1.Pgcluster, username string) error { +// disablePostgreSQLLogin disables the ability for a PostgreSQL user to log in +func disablePostgreSQLLogin(clientset kubernetes.Interface, restconfig *rest.Config, cluster *crv1.Pgcluster, username string) error { log.Debugf("disable user %q on cluster %q", username, cluster.Name) // disable the pgbouncer user in the PostgreSQL cluster. // first, get the primary pod. If we cannot do this, let's consider it an @@ -81,9 +81,9 @@ func generatePassword() (string, error) { return util.GeneratePassword(generatedPasswordLength) } -// makePostgresPassword creates the expected hash for a password type for a +// makePostgreSQLPassword creates the expected hash for a password type for a // PostgreSQL password -func makePostgresPassword(passwordType pgpassword.PasswordType, username, password string) string { +func makePostgreSQLPassword(passwordType pgpassword.PasswordType, username, password string) string { // get the PostgreSQL password generate based on the password type // as all of these values are valid, this not not error postgresPassword, _ := pgpassword.NewPostgresPassword(passwordType, username, password) @@ -94,6 +94,30 @@ func makePostgresPassword(passwordType pgpassword.PasswordType, username, passwo return hashedPassword } +// rotatePostgreSQLPassword generates a new password for the specified +// username/Secret pair and saves it both in PostgreSQL and the Secret itself +func rotatePostgreSQLPassword(clientset kubernetes.Interface, restconfig *rest.Config, cluster *crv1.Pgcluster, + username string) (string, error) { + // determine if we are able to access the primary Pod + pod, err := util.GetPrimaryPod(clientset, cluster) + if err != nil { + return "", err + } + + // generate a new password + password, err := generatePassword() + if err != nil { + return "", err + } + + // update the PostgreSQL instance with the new password. + if err := setPostgreSQLPassword(clientset, restconfig, pod, cluster.Spec.Port, username, password); err != nil { + return "", err + } + + return password, err +} + // setPostgreSQLPassword updates the password of a user in a PostgreSQL // cluster by executing into the Pod provided (i.e. a primary) and changing it func setPostgreSQLPassword(clientset kubernetes.Interface, restconfig *rest.Config, pod *v1.Pod, port, @@ -103,7 +127,7 @@ func setPostgreSQLPassword(clientset kubernetes.Interface, restconfig *rest.Conf // we use the PostgreSQL "md5" hashing mechanism here to pre-hash the // password. This is semi-hard coded but is now prepped for SCRAM as a // password type can be passed in. Almost to SCRAM! - passwordHash := makePostgresPassword(pgpassword.MD5, username, password) + passwordHash := makePostgreSQLPassword(pgpassword.MD5, username, password) if err := util.SetPostgreSQLPassword(clientset, restconfig, pod, port, username, passwordHash, sqlEnableLogin); err != nil { diff --git a/internal/operator/cluster/common_test.go b/internal/operator/cluster/common_test.go index 24f0423969..8b83becb80 100644 --- a/internal/operator/cluster/common_test.go +++ b/internal/operator/cluster/common_test.go @@ -29,7 +29,7 @@ func TestMakePostgresPassword(t *testing.T) { password := "datalake" expected := "md56294153764d389dc6830b6ce4f923cdb" - actual := makePostgresPassword(passwordType, username, password) + actual := makePostgreSQLPassword(passwordType, username, password) if actual != expected { t.Errorf("expected: %q actual: %q", expected, actual) diff --git a/internal/operator/cluster/exporter.go b/internal/operator/cluster/exporter.go index 957e47a7d1..1da55df006 100644 --- a/internal/operator/cluster/exporter.go +++ b/internal/operator/cluster/exporter.go @@ -99,7 +99,6 @@ func AddExporter(clientset kubernetes.Interface, restconfig *rest.Config, cluste return err } - // after the secret if this is a standby, exit early // this can't be installed if this is a standby, so abort if that's the case if cluster.Spec.Standby { return ErrStandbyNotAllowed @@ -217,7 +216,7 @@ func RemoveExporter(clientset kubernetes.Interface, restconfig *rest.Config, clu // if this is a standby cluster, return as we cannot execute any SQL if !cluster.Spec.Standby { // if this fails, warn and continue - if err := disablePostgresLogin(clientset, restconfig, cluster, crv1.PGUserMonitor); err != nil { + if err := disablePostgreSQLLogin(clientset, restconfig, cluster, crv1.PGUserMonitor); err != nil { log.Warn(err) } } @@ -232,6 +231,38 @@ func RemoveExporter(clientset kubernetes.Interface, restconfig *rest.Config, clu return nil } +// RotateExporterPassword rotates the password for the monitoring PostgreSQL +// user +func RotateExporterPassword(clientset kubernetes.Interface, restconfig *rest.Config, cluster *crv1.Pgcluster) error { + ctx := context.TODO() + + // let's also go ahead and get the secret that contains the pgBouncer + // information. If we can't find the secret, we're basically done here + secretName := util.GenerateExporterSecretName(cluster.Name) + secret, err := clientset.CoreV1().Secrets(cluster.Namespace).Get(ctx, secretName, metav1.GetOptions{}) + if err != nil { + return err + } + + // update the password on the PostgreSQL instance + password, err := rotatePostgreSQLPassword(clientset, restconfig, cluster, crv1.PGUserMonitor) + if err != nil { + return err + } + + // next, update the password field of the secret. + secret.Data["password"] = []byte(password) + + // update the secret + if _, err := clientset.CoreV1().Secrets(cluster.Namespace). + Update(ctx, secret, metav1.UpdateOptions{}); err != nil { + return err + } + + // and that's it - the changes will be propagated to the exporter sidecars + return nil +} + // UpdateExporterSidecar either adds or emoves the metrics sidcar from the // cluster. This is meant to be used as a rolling update callback function func UpdateExporterSidecar(cluster *crv1.Pgcluster, deployment *appsv1.Deployment) error { diff --git a/internal/operator/cluster/pgbouncer.go b/internal/operator/cluster/pgbouncer.go index a9a6d5ae2a..0a78e305f2 100644 --- a/internal/operator/cluster/pgbouncer.go +++ b/internal/operator/cluster/pgbouncer.go @@ -104,11 +104,9 @@ const ( sqlGetDatabasesForPgBouncer = `SELECT datname FROM pg_catalog.pg_database WHERE datname NOT IN ('template0') AND datallowconn;` ) -var ( - // sqlUninstallPgBouncer provides the final piece of SQL to uninstall - // pgbouncer, which is to remove the user - sqlUninstallPgBouncer = fmt.Sprintf(`DROP ROLE "%s";`, crv1.PGUserPgBouncer) -) +// sqlUninstallPgBouncer provides the final piece of SQL to uninstall +// pgbouncer, which is to remove the user +var sqlUninstallPgBouncer = fmt.Sprintf(`DROP ROLE "%s";`, crv1.PGUserPgBouncer) // AddPgbouncer contains the various functions that are used to add a pgBouncer // Deployment to a PostgreSQL cluster @@ -120,7 +118,6 @@ func AddPgbouncer(clientset kubernetes.Interface, restconfig *rest.Config, clust // get the primary pod, which is needed to update the password for the // pgBouncer administrative user pod, err := util.GetPrimaryPod(clientset, cluster) - if err != nil { return err } @@ -146,11 +143,9 @@ func AddPgbouncer(clientset kubernetes.Interface, restconfig *rest.Config, clust if !cluster.Spec.Standby { secretName := util.GeneratePgBouncerSecretName(cluster.Name) pgBouncerPassword, err := util.GetPasswordFromSecret(clientset, cluster.Namespace, secretName) - if err != nil { // set the password that will be used for the "pgbouncer" PostgreSQL account newPassword, err := generatePassword() - if err != nil { return err } @@ -267,22 +262,6 @@ func DeletePgbouncer(clientset kubernetes.Interface, restconfig *rest.Config, cl func RotatePgBouncerPassword(clientset kubernetes.Interface, restconfig *rest.Config, cluster *crv1.Pgcluster) error { ctx := context.TODO() - // determine if we are able to access the primary Pod - primaryPod, err := util.GetPrimaryPod(clientset, cluster) - - if err != nil { - return err - } - - // let's also go ahead and get the secret that contains the pgBouncer - // information. If we can't find the secret, we're basically done here - secretName := util.GeneratePgBouncerSecretName(cluster.Name) - secret, err := clientset.CoreV1().Secrets(cluster.Namespace).Get(ctx, secretName, metav1.GetOptions{}) - - if err != nil { - return err - } - // there are a few steps that must occur in order for the password to be // successfully rotated: // @@ -294,16 +273,17 @@ func RotatePgBouncerPassword(clientset kubernetes.Interface, restconfig *rest.Co // ...wouldn't it be nice if we could run this in a transaction? rolling back // is hard :( - // first, generate a new password - password, err := generatePassword() - + // let's also go ahead and get the secret that contains the pgBouncer + // information. If we can't find the secret, we're basically done here + secretName := util.GeneratePgBouncerSecretName(cluster.Name) + secret, err := clientset.CoreV1().Secrets(cluster.Namespace).Get(ctx, secretName, metav1.GetOptions{}) if err != nil { return err } - // next, update the PostgreSQL primary with the new password. If this fails - // we definitely return an error - if err := setPostgreSQLPassword(clientset, restconfig, primaryPod, cluster.Spec.Port, crv1.PGUserPgBouncer, password); err != nil { + // update the password on the PostgreSQL instance + password, err := rotatePostgreSQLPassword(clientset, restconfig, cluster, crv1.PGUserPgBouncer) + if err != nil { return err } @@ -312,7 +292,7 @@ func RotatePgBouncerPassword(clientset kubernetes.Interface, restconfig *rest.Co // PostgreSQL to perform its authentication secret.Data["password"] = []byte(password) secret.Data["users.txt"] = util.GeneratePgBouncerUsersFileBytes( - makePostgresPassword(pgpassword.MD5, crv1.PGUserPgBouncer, password)) + makePostgreSQLPassword(pgpassword.MD5, crv1.PGUserPgBouncer, password)) // update the secret if _, err := clientset.CoreV1().Secrets(cluster.Namespace). @@ -351,14 +331,12 @@ func UninstallPgBouncer(clientset kubernetes.Interface, restconfig *rest.Config, // determine if we are able to access the primary Pod. If not, then the // journey ends right here pod, err := util.GetPrimaryPod(clientset, cluster) - if err != nil { return err } // get the list of databases that we need to scan through databases, err := getPgBouncerDatabases(clientset, restconfig, pod, cluster.Spec.Port) - if err != nil { return err } @@ -379,7 +357,6 @@ func UninstallPgBouncer(clientset kubernetes.Interface, restconfig *rest.Config, // exec into the pod to run the query _, stderr, err := kubeapi.ExecToPodThroughAPI(restconfig, clientset, cmd, "database", pod.Name, pod.ObjectMeta.Namespace, sql) - // if there is an error executing the command, log the error message from // stderr and return the error if err != nil { @@ -439,7 +416,6 @@ func UpdatePgBouncerAnnotations(clientset kubernetes.Interface, cluster *crv1.Pg // get a list of all of the instance deployments for the cluster deployment, err := getPgBouncerDeployment(clientset, cluster) - if err != nil { return err } @@ -473,7 +449,6 @@ func checkPgBouncerInstall(clientset kubernetes.Interface, restconfig *rest.Conf // exec into the pod to run the query stdout, stderr, err := kubeapi.ExecToPodThroughAPI(restconfig, clientset, cmd, "database", pod.Name, pod.ObjectMeta.Namespace, sql) - // if there is an error executing the command, log the error message from // stderr and return the error if err != nil { @@ -506,7 +481,6 @@ func createPgbouncerConfigMap(clientset kubernetes.Interface, cluster *crv1.Pgcl // generate the pgbouncer.ini information pgBouncerConf, err := generatePgBouncerConf(cluster) - if err != nil { log.Error(err) return err @@ -514,7 +488,6 @@ func createPgbouncerConfigMap(clientset kubernetes.Interface, cluster *crv1.Pgcl // generate the pgbouncer HBA file pgbouncerHBA, err := generatePgBouncerHBA(cluster) - if err != nil { log.Error(err) return err @@ -644,7 +617,7 @@ func createPgbouncerSecret(clientset kubernetes.Interface, cluster *crv1.Pgclust Data: map[string][]byte{ "password": []byte(password), "users.txt": util.GeneratePgBouncerUsersFileBytes( - makePostgresPassword(pgpassword.MD5, crv1.PGUserPgBouncer, password)), + makePostgreSQLPassword(pgpassword.MD5, crv1.PGUserPgBouncer, password)), }, } @@ -684,7 +657,7 @@ func createPgBouncerService(clientset kubernetes.Interface, cluster *crv1.Pgclus // disable the "pgbouncer" role from being able to log in. It keeps the // artificats that were created during normal pgBouncer operation func disablePgBouncer(clientset kubernetes.Interface, restconfig *rest.Config, cluster *crv1.Pgcluster) error { - return disablePostgresLogin(clientset, restconfig, cluster, crv1.PGUserPgBouncer) + return disablePostgreSQLLogin(clientset, restconfig, cluster, crv1.PGUserPgBouncer) } // execPgBouncerScript runs a script pertaining to the management of pgBouncer @@ -695,7 +668,6 @@ func execPgBouncerScript(clientset kubernetes.Interface, restconfig *rest.Config // exec into the pod to run the query _, stderr, err := kubeapi.ExecToPodThroughAPI(restconfig, clientset, cmd, "database", pod.Name, pod.ObjectMeta.Namespace, nil) - // if there is an error executing the command, log the error as a warning // that it failed, and continue. It's hard to rollback from this one :\ if err != nil { @@ -773,7 +745,6 @@ func getPgBouncerDatabases(clientset kubernetes.Interface, restconfig *rest.Conf // exec into the pod to run the query stdout, stderr, err := kubeapi.ExecToPodThroughAPI(restconfig, clientset, cmd, "database", pod.Name, pod.ObjectMeta.Namespace, sql) - // if there is an error executing the command, log the error message from // stderr and return the error if err != nil { @@ -796,7 +767,6 @@ func getPgBouncerDeployment(clientset kubernetes.Interface, cluster *crv1.Pgclus pgbouncerDeploymentName := fmt.Sprintf(pgBouncerDeploymentFormat, cluster.Name) deployment, err := clientset.AppsV1().Deployments(cluster.Namespace).Get(ctx, pgbouncerDeploymentName, metav1.GetOptions{}) - if err != nil { return nil, err } @@ -809,7 +779,6 @@ func getPgBouncerDeployment(clientset kubernetes.Interface, cluster *crv1.Pgclus func installPgBouncer(clientset kubernetes.Interface, restconfig *rest.Config, pod *v1.Pod, port string) error { // get the list of databases that we need to scan through databases, err := getPgBouncerDatabases(clientset, restconfig, pod, port) - if err != nil { return err } @@ -881,7 +850,6 @@ func updatePgBouncerReplicas(clientset kubernetes.Interface, cluster *crv1.Pgclu // get the pgBouncer deployment so the resources can be updated deployment, err := getPgBouncerDeployment(clientset, cluster) - if err != nil { return err } @@ -907,7 +875,6 @@ func updatePgBouncerResources(clientset kubernetes.Interface, cluster *crv1.Pgcl // get the pgBouncer deployment so the resources can be updated deployment, err := getPgBouncerDeployment(clientset, cluster) - if err != nil { return err } diff --git a/internal/operator/clusterutilities.go b/internal/operator/clusterutilities.go index 309f6d5689..8df208a7ad 100644 --- a/internal/operator/clusterutilities.go +++ b/internal/operator/clusterutilities.go @@ -366,8 +366,6 @@ func GetExporterAddon(spec crv1.PgclusterSpec) string { return "" } - log.Debug("crunchy-postgres-exporter was found as a label on cluster create") - exporterTemplateFields := exporterTemplateFields{ ContainerResources: GetResourcesJSON(spec.ExporterResources, spec.ExporterLimits), ExporterPort: spec.ExporterPort, diff --git a/pkg/apiservermsgs/clustermsgs.go b/pkg/apiservermsgs/clustermsgs.go index c83ffb6e25..251e32c7cd 100644 --- a/pkg/apiservermsgs/clustermsgs.go +++ b/pkg/apiservermsgs/clustermsgs.go @@ -423,6 +423,9 @@ type UpdateClusterRequest struct { // ExporterMemoryRequest, if specified, is the value of how much RAM should // be requested for the Crunchy Postgres Exporter instance. ExporterMemoryRequest string + // ExporterRotatePassword, if specified, rotates the password of the metrics + // collection agent, i.e. the "ccp_monitoring" user. + ExporterRotatePassword bool // CPULimit is the value of the max CPU utilization for a Pod that has a // PostgreSQL cluster CPULimit string From 7b51d0df674e089b7f0d8f499edd5d093b83621e Mon Sep 17 00:00:00 2001 From: andrewlecuyer <43458182+andrewlecuyer@users.noreply.github.com> Date: Fri, 18 Dec 2020 07:30:08 -0600 Subject: [PATCH 050/276] Wait for PG Deployment Deletion on Restore Since the primary PVC for the cluster is now retained during an in-place PostgreSQL cluster restore in support of pgBackRest delta restores, when preparing a cluster for a restore we can no longer rely on the deletion of all PVC's as an indicator that the 'config' and 'leader' ConfigMaps created by Patroni can be removed. Therefore, the Operator now specifically waits for all Deployments to be successfully removed prior to deleting these resources. Issue: [ch9926] --- internal/operator/backrest/restore.go | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/internal/operator/backrest/restore.go b/internal/operator/backrest/restore.go index 8107bcc09f..1f23802deb 100644 --- a/internal/operator/backrest/restore.go +++ b/internal/operator/backrest/restore.go @@ -184,7 +184,24 @@ func PrepareClusterForRestore(clientset kubeapi.Interface, cluster *crv1.Pgclust }); err != nil { return nil, err } - log.Debugf("restore workflow: deleted primary and replicas %v", pgInstances) + log.Debugf("restore workflow: deleted primary and replica deployments for cluster %s", + clusterName) + + // Wait for all primary and replica deployments to be removed. If unable to verify that all + // deployments have been removed, then the restore cannot proceed and the function returns. + if err := wait.Poll(time.Second/2, time.Minute*3, func() (bool, error) { + for _, deployment := range pgInstances.Items { + if _, err := clientset.AppsV1().Deployments(namespace). + Get(ctx, deployment.GetName(), metav1.GetOptions{}); err == nil || !kerrors.IsNotFound(err) { + return false, nil + } + } + return true, nil + }); err != nil { + return nil, err + } + log.Debugf("restore workflow: finished waiting for primary and replica deployments for "+ + "cluster %s to be removed", clusterName) // delete all existing jobs deletePropagation := metav1.DeletePropagationBackground From 475d57cbe6e6ef221d329829db1eb508ebf7fe0a Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Fri, 18 Dec 2020 15:00:24 -0500 Subject: [PATCH 051/276] Add a "build" Makefile target This is a convenience for development, allowing all of the Golang binaries to be built from a single target. --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 1488c2dc7c..1411047cb5 100644 --- a/Makefile +++ b/Makefile @@ -112,6 +112,8 @@ deployoperator: #======= Binary builds ======= +build: build-postgres-operator build-pgo-apiserver build-pgo-client build-pgo-rmdata build-pgo-scheduler + build-pgo-apiserver: $(GO_BUILD) -o bin/apiserver ./cmd/apiserver From 693a78691d116786c7cb13eb1b56ad414dcb6604 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Fri, 18 Dec 2020 16:35:02 -0500 Subject: [PATCH 052/276] Fix several edge case out-of-index panics While these should rarely, if ever, happen, the world of distributed computing is unpredictable and we should ensure our code can fail gracefully in these scenarios. --- internal/operator/cluster/failoverlogic.go | 2 ++ internal/operator/clusterutilities.go | 3 +++ 2 files changed, 5 insertions(+) diff --git a/internal/operator/cluster/failoverlogic.go b/internal/operator/cluster/failoverlogic.go index 6462002ad7..4ffa67e4d4 100644 --- a/internal/operator/cluster/failoverlogic.go +++ b/internal/operator/cluster/failoverlogic.go @@ -210,6 +210,8 @@ func RemovePrimaryOnRoleChangeTag(clientset kubernetes.Interface, restconfig *re if err != nil { log.Error(err) return err + } else if len(pods.Items) == 0 { + return fmt.Errorf("no pods found for cluster %q", clusterName) } else if len(pods.Items) > 1 { log.Error("More than one primary found after completing the post-failover backup") } diff --git a/internal/operator/clusterutilities.go b/internal/operator/clusterutilities.go index 8df208a7ad..030e706404 100644 --- a/internal/operator/clusterutilities.go +++ b/internal/operator/clusterutilities.go @@ -941,6 +941,9 @@ func UpdatePGHAConfigInitFlag(clientset kubernetes.Interface, initVal bool, clus case err != nil: return fmt.Errorf("unable to find the default pgha configMap found for cluster %s using selector %s, unable to set "+ "init value to false", clusterName, selector) + case len(configMapList.Items) == 0: + return fmt.Errorf("no pgha configMaps found for cluster %s using selector %s, unable to set "+ + "init value to false", clusterName, selector) case len(configMapList.Items) > 1: return fmt.Errorf("more than one default pgha configMap found for cluster %s using selector %s, unable to set "+ "init value to false", clusterName, selector) From c209b16c6d9a88a83349b05198676df516df693a Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Fri, 18 Dec 2020 16:36:12 -0500 Subject: [PATCH 053/276] Modify syntax for checking for recovery status via SQL (#2133) There were cases where this was failing due to too many quotes being used, so this should avoid said issues. Issue: [ch9981] Issue: #2108 --- internal/controller/pod/promotionhandler.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/controller/pod/promotionhandler.go b/internal/controller/pod/promotionhandler.go index 2585f50653..e420af589c 100644 --- a/internal/controller/pod/promotionhandler.go +++ b/internal/controller/pod/promotionhandler.go @@ -36,9 +36,14 @@ import ( "k8s.io/client-go/rest" ) +const ( + // recoverySQL is just the SQL to figure out if Postgres is in recovery mode + recoverySQL = "SELECT pg_is_in_recovery();" +) + var ( // isInRecoveryCommand is the command run to determine if postgres is in recovery - isInRecoveryCMD []string = []string{"psql", "-t", "-c", "'SELECT pg_is_in_recovery();'", "-p"} + isInRecoveryCMD []string = []string{"psql", "-t", "-c", recoverySQL, "-p"} // leaderStatusCMD is the command run to get the Patroni status for the primary leaderStatusCMD []string = []string{"curl", fmt.Sprintf("localhost:%s/master", From 2fba41ae0759a64fddfc746c85958c24b35f263f Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sat, 19 Dec 2020 16:51:05 -0500 Subject: [PATCH 054/276] Bump Grafana & Prometheus versions for Postgres Operator Monitoring This moves Grafana to 6.7.5 and Prometheus to 2.23.0. Note that this continues to use the upstream version. --- .../content/installation/metrics/metrics-configuration.md | 8 ++++---- .../metrics/ansible/roles/pgo-metrics/defaults/main.yml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/content/installation/metrics/metrics-configuration.md b/docs/content/installation/metrics/metrics-configuration.md index 7d343480cf..559f9273ef 100644 --- a/docs/content/installation/metrics/metrics-configuration.md +++ b/docs/content/installation/metrics/metrics-configuration.md @@ -108,10 +108,10 @@ and tag as needed to use the RedHat certified containers: | `alertmanager_image_tag` | v0.21.0 | **Required** | Configures the image tag to use for the Alertmanager container. | | `grafana_image_prefix` | grafana | **Required** | Configures the image prefix to use for the Grafana container.| | `grafana_image_name` | grafana | **Required** | Configures the image name to use for the Grafana container. | -| `grafana_image_tag` | 6.7.4 | **Required** | Configures the image tag to use for the Grafana container. | +| `grafana_image_tag` | 6.7.5 | **Required** | Configures the image tag to use for the Grafana container. | | `prometheus_image_prefix` | prom | **Required** | Configures the image prefix to use for the Prometheus container. | | `prometheus_image_name` | promtheus | **Required** | Configures the image name to use for the Prometheus container. | -| `prometheus_image_tag` | v2.20.0 | **Required** | Configures the image tag to use for the Prometheus container. | +| `prometheus_image_tag` | v2.23.0 | **Required** | Configures the image tag to use for the Prometheus container. | Additionally, these same settings can be utilized as needed to support custom image names, tags, and additional container registries. @@ -124,7 +124,7 @@ PostgreSQL Operator Monitoring infrastructure: | Name | Default | Required | Description | |------|---------|----------|-------------| -| `pgo_image_prefix` | registry.developers.crunchydata.com/crunchydata | **Required** | Configures the image prefix used by the `pgo-deployer` container | +| `pgo_image_prefix` | registry.developers.crunchydata.com/crunchydata | **Required** | Configures the image prefix used by the `pgo-deployer` container | | `pgo_image_tag` | {{< param centosBase >}}-{{< param operatorVersion >}} | **Required** | Configures the image tag used by the `pgo-deployer` container | -[k8s-service-type]: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types \ No newline at end of file +[k8s-service-type]: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types diff --git a/installers/metrics/ansible/roles/pgo-metrics/defaults/main.yml b/installers/metrics/ansible/roles/pgo-metrics/defaults/main.yml index 775d6691f5..a16a017d63 100644 --- a/installers/metrics/ansible/roles/pgo-metrics/defaults/main.yml +++ b/installers/metrics/ansible/roles/pgo-metrics/defaults/main.yml @@ -29,7 +29,7 @@ grafana_admin_password: "" grafana_install: "true" grafana_image_prefix: "grafana" grafana_image_name: "grafana" -grafana_image_tag: "6.7.4" +grafana_image_tag: "6.7.5" grafana_port: "3000" grafana_service_name: "crunchy-grafana" grafana_service_type: "ClusterIP" @@ -45,7 +45,7 @@ prometheus_custom_config: "" prometheus_install: "true" prometheus_image_prefix: "prom" prometheus_image_name: "prometheus" -prometheus_image_tag: "v2.20.0" +prometheus_image_tag: "v2.23.0" prometheus_port: "9090" prometheus_service_name: "crunchy-prometheus" prometheus_service_type: "ClusterIP" From 4e5323659f3d4bbf4e39cb8ed8f210d32a860812 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sat, 19 Dec 2020 17:15:04 -0500 Subject: [PATCH 055/276] Provide more documentation on metrics enablement This adds examples to the monitoring architecture and tutorial around how to enable metrics collection on an existing PostgreSQL cluster. --- docs/content/architecture/monitoring.md | 8 ++++++++ docs/content/tutorial/customize-cluster.md | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/docs/content/architecture/monitoring.md b/docs/content/architecture/monitoring.md index 75c9b0eb5f..9258b18f33 100644 --- a/docs/content/architecture/monitoring.md +++ b/docs/content/architecture/monitoring.md @@ -35,6 +35,14 @@ command, for example: pgo create cluster --metrics hippo ``` +If you have already created a cluster and want to add metrics collection to it, +you can use the `--enable-metrics` flag as part of the [`pgo update cluster`]({{< relref "pgo-client/reference/pgo_update_cluster.md" >}}) +command, for example: + +``` +pgo update cluster --enable-metrics hippo +``` + ## Components The [PostgreSQL Operator Monitoring]({{< relref "installation/metrics/_index.md" >}}) diff --git a/docs/content/tutorial/customize-cluster.md b/docs/content/tutorial/customize-cluster.md index 2fee92bb0a..3b30d0f0f2 100644 --- a/docs/content/tutorial/customize-cluster.md +++ b/docs/content/tutorial/customize-cluster.md @@ -30,6 +30,12 @@ pgo create cluster hippo --metrics Note that the `--metrics` flag just enables a sidecar that can be scraped. You will need to install the [monitoring stack]({{< relref "installation/metrics/_index.md" >}}) separately, or tie it into your existing monitoring infrastructure. +If you have an exiting cluster that you would like to add metrics collection to, you can use the `--enable-metrics` flag on the [`pgo update cluster`]({{< relref "pgo-client/reference/pgo_create_cluster.md" >}}) command: + +``` +pgo update cluster hippo --enable-metrics +``` + ## Customize PVC Size Databases come in all different sizes, and those sizes can certainly change over time. As such, it is helpful to be able to specify what size PVC you want to store your PostgreSQL data. From 36e10018e3812b88f0dafee78178bd37ea3c0196 Mon Sep 17 00:00:00 2001 From: Val Date: Sun, 20 Dec 2020 11:15:53 -0500 Subject: [PATCH 056/276] Add Kustomize example for creating a Postgres cluster This adds an example for using the Kustomize configuration management tool for how to manage a custom resource attributed to pgclusters.crunchydata.com. --- examples/kustomize/createcluster/README.md | 187 ++++++++++++++++++ .../createcluster/base/kustomization.yaml | 27 +++ .../createcluster/base/pgcluster.yaml | 105 ++++++++++ .../createcluster/overlay/dev/bouncer.json | 4 + .../createcluster/overlay/dev/devhippo.json | 18 ++ .../overlay/dev/kustomization.yaml | 22 +++ .../overlay/prod/kustomization.yaml | 15 ++ .../createcluster/overlay/prod/prodhippo.json | 19 ++ .../overlay/staging/annotations.json | 6 + .../overlay/staging/hippo-rpl1-pgreplica.yaml | 27 +++ .../overlay/staging/kustomization.yaml | 23 +++ .../overlay/staging/staginghippo.json | 19 ++ 12 files changed, 472 insertions(+) create mode 100644 examples/kustomize/createcluster/README.md create mode 100644 examples/kustomize/createcluster/base/kustomization.yaml create mode 100644 examples/kustomize/createcluster/base/pgcluster.yaml create mode 100644 examples/kustomize/createcluster/overlay/dev/bouncer.json create mode 100644 examples/kustomize/createcluster/overlay/dev/devhippo.json create mode 100644 examples/kustomize/createcluster/overlay/dev/kustomization.yaml create mode 100644 examples/kustomize/createcluster/overlay/prod/kustomization.yaml create mode 100644 examples/kustomize/createcluster/overlay/prod/prodhippo.json create mode 100644 examples/kustomize/createcluster/overlay/staging/annotations.json create mode 100644 examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml create mode 100644 examples/kustomize/createcluster/overlay/staging/kustomization.yaml create mode 100644 examples/kustomize/createcluster/overlay/staging/staginghippo.json diff --git a/examples/kustomize/createcluster/README.md b/examples/kustomize/createcluster/README.md new file mode 100644 index 0000000000..0bfc762305 --- /dev/null +++ b/examples/kustomize/createcluster/README.md @@ -0,0 +1,187 @@ +# create cluster +This is a working example that creates multiple clusters via the crd workflow using +kustomize. + +## Prerequisites + +### Postgres Operator +This example assumes you have the Crunchy PostgreSQL Operator installed +in a namespace called `pgo`. + +### Kustomize +Install the latest [kustomize](https://kubectl.docs.kubernetes.io/installation/kustomize/) version available. Kustomise is availble in kubectl but it will not be the latest version. + +## Documenation +Please see the [documentation](https://access.crunchydata.com/documentation/postgres-operator/latest/custom-resources/) for more guidance using custom resources. + +## Example set up and execution +Navigate to the createcluster directory under the examples/kustomize directory +``` +cd ./examples/kustomize/createcluster/ +``` +In the createcluster directory you will see a base directory and an overlay directory. Base will create a simple crunchy data postgreSQL cluster. There are 3 directories located in the overlay directory, dev, staging and prod. You can run kustomize against each of those and a Crunchy PostgreSQL cluster will be created for each and each of them are slightly different. + +### base +Lets generate the kustomize yaml for the base +``` +kustomize build base/ +``` +If the yaml looks good lets apply it. +``` +kustomize build base/ | kubectl apply -f - +``` +You will see these items are created after running the above command +``` +secret/hippo-hippo-secret created +secret/hippo-postgres-secret created +secret/hippo-primaryuser-secret created +pgcluster.crunchydata.com/hippo created +``` +You may need to wait a few seconds depending on the resources you have allocated to you kubernetes set up for the Crunchy PostgreSQL cluster to become available. + +After the cluster is finished creating lets take a look at the cluster with the Crunchy PostgreSQL Operator +``` +pgo show cluster hippo -n pgo +``` +You will see something like this if successful: +``` +cluster : hippo (crunchy-postgres-ha:centos7-12.5-4.5.1) + pod : hippo-8fb6bd96-j87wq (Running) on gke-xxxx-default-pool-38e946bd-257w (1/1) (primary) + pvc: hippo (1Gi) + deployment : hippo + deployment : hippo-backrest-shared-repo + service : hippo - ClusterIP (10.0.56.86) - Ports (2022/TCP, 5432/TCP) + labels : pg-pod-anti-affinity= pgo-backrest=true pgo-version=4.5.1 crunchy-postgres-exporter=false name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata autofail=true crunchy-pgbadger=false +``` +Feel free to run other pgo cli commands on the hippo cluster + +### overlay +As mentioned above there are 3 overlays available in this example, these overlays will modify the common base. +#### development +The development overlay will deploy a simple Crunchy PostgreSQL cluster with pgbouncer + +Lets generate the kustomize yaml for the dev overlay +``` +kustomize build overlay/dev/ +``` +The yaml looks good now lets apply it +``` +kustomize build overlay/dev/ | kubectl apply -f - +``` +You will see these items are created after running the above command +``` +secret/dev-hippo-hippo-secret created +secret/dev-hippo-postgres-secret created +secret/dev-hippo-primaryuser-secret created +pgcluster.crunchydata.com/dev-hippo created +``` +After the cluster is finished creating lets take a look at the cluster with the Crunchy PostgreSQL Operator +``` +pgo show cluster dev-hippo -n pgo +``` +You will see something like this if successful: +``` +cluster : dev-hippo (crunchy-postgres-ha:centos7-12.5-4.5.1) + pod : dev-hippo-588d4cb746-bwrxb (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) + pvc: dev-hippo (1Gi) + deployment : dev-hippo + deployment : dev-hippo-backrest-shared-repo + deployment : dev-hippo-pgbouncer + service : dev-hippo - ClusterIP (10.0.62.87) - Ports (2022/TCP, 5432/TCP) + service : dev-hippo-pgbouncer - ClusterIP (10.0.48.120) - Ports (5432/TCP) + labels : crunchy-pgha-scope=dev-hippo crunchy-postgres-exporter=false name=dev-hippo pg-cluster=dev-hippo pg-pod-anti-affinity= pgo-backrest=true vendor=crunchydata autofail=true crunchy-pgbadger=false deployment-name=dev-hippo environment=development pgo-version=4.5.1 pgouser=admin +``` +#### staging +The staging overlay will deploy a crunchy postgreSQL cluster with 2 replica's with annotations added + +Lets generate the kustomize yaml for the staging overlay +``` +kustomize build overlay/staging/ +``` +The yaml looks good now lets apply it +``` +kustomize build overlay/staging/ | kubectl apply -f - +``` +You will see these items are created after running the above command +``` +secret/staging-hippo-hippo-secret created +secret/staging-hippo-postgres-secret created +secret/staging-hippo-primaryuser-secret created +pgcluster.crunchydata.com/staging-hippo created +pgreplica.crunchydata.com/staging-hippo-rpl1 created +``` +After the cluster is finished creating lets take a look at the cluster with the crunchy postgreSQL operator +``` +pgo show cluster staging-hippo -n pgo +``` +You will see something like this if successful, (Notice one of the replicas is a different size): +``` +cluster : staging-hippo (crunchy-postgres-ha:centos7-12.5-4.5.1) + pod : staging-hippo-85cf6dcb65-9h748 (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) + pvc: staging-hippo (1Gi) + pod : staging-hippo-lnxw-cf47d8c8b-6r4wn (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (replica) + pvc: staging-hippo-lnxw (1Gi) + pod : staging-hippo-rpl1-5d89d66f9b-44znd (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (replica) + pvc: staging-hippo-rpl1 (2Gi) + deployment : staging-hippo + deployment : staging-hippo-backrest-shared-repo + deployment : staging-hippo-lnxw + deployment : staging-hippo-rpl1 + service : staging-hippo - ClusterIP (10.0.56.253) - Ports (2022/TCP, 5432/TCP) + service : staging-hippo-replica - ClusterIP (10.0.56.57) - Ports (2022/TCP, 5432/TCP) + pgreplica : staging-hippo-lnxw + pgreplica : staging-hippo-rpl1 + labels : deployment-name=staging-hippo environment=staging name=staging-hippo pg-pod-anti-affinity= crunchy-postgres-exporter=false crunchy-pgbadger=false crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-backrest=true pgo-version=4.5.1 pgouser=admin vendor=crunchydata autofail=true +``` + +#### production +The production overlay will deploy a crunchy postgreSQL cluster with one replica + +Lets generate the kustomize yaml for the prod overlay +``` +kustomize build overlay/prod/ +``` +The yaml looks good now lets apply it +``` +kustomize build overlay/prod/ | kubectl apply -f - +``` +You will see these items are created after running the above command +``` +secret/prod-hippo-hippo-secret created +secret/prod-hippo-postgres-secret created +secret/prod-hippo-primaryuser-secret created +pgcluster.crunchydata.com/prod-hippo created +``` +After the cluster is finished creating lets take a look at the cluster with the crunchy postgreSQL operator +``` +pgo show cluster prod-hippo -n pgo +``` +You will see something like this if successful, (Notice one of the replicas is a different size): +``` +cluster : prod-hippo (crunchy-postgres-ha:centos7-12.5-4.5.1) + pod : prod-hippo-5d6dd46497-rr67c (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (primary) + pvc: prod-hippo (1Gi) + pod : prod-hippo-flty-84d97c8769-2pzbh (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (replica) + pvc: prod-hippo-flty (1Gi) + deployment : prod-hippo + deployment : prod-hippo-backrest-shared-repo + deployment : prod-hippo-flty + service : prod-hippo - ClusterIP (10.0.56.18) - Ports (2022/TCP, 5432/TCP) + service : prod-hippo-replica - ClusterIP (10.0.56.101) - Ports (2022/TCP, 5432/TCP) + pgreplica : prod-hippo-flty + labels : pgo-backrest=true pgo-version=4.5.1 crunchy-pgbadger=false crunchy-postgres-exporter=false deployment-name=prod-hippo environment=production pg-cluster=prod-hippo pg-pod-anti-affinity= autofail=true crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata +``` +### Delete the clusters +To delete the clusters run the following pgo cli commands + +To delete all the clusters in the `pgo` namespace run the following: +``` +pgo delete cluster --all -n pgo +``` +Or to delete each cluster individually +``` +pgo delete cluster hippo -n pgo +pgo delete cluster dev-hippo -n pgo +pgo delete cluster staging-hippo -n pgo +pgo delete cluster prod-hippo -n pgo +``` \ No newline at end of file diff --git a/examples/kustomize/createcluster/base/kustomization.yaml b/examples/kustomize/createcluster/base/kustomization.yaml new file mode 100644 index 0000000000..a93d6f8eaa --- /dev/null +++ b/examples/kustomize/createcluster/base/kustomization.yaml @@ -0,0 +1,27 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: pgo +commonLabels: + vendor: crunchydata +secretGenerator: + - name: hippo-hippo-secret + options: + disableNameSuffixHash: true + literals: + - username=hippo + - password=Moresecurepassword* + - name: hippo-primaryuser-secret + options: + disableNameSuffixHash: true + literals: + - username=primaryuser + - password=Anothersecurepassword* + - name: hippo-postgres-secret + options: + disableNameSuffixHash: true + literals: + - username=postgres + - password=Supersecurepassword* +resources: +- pgcluster.yaml + diff --git a/examples/kustomize/createcluster/base/pgcluster.yaml b/examples/kustomize/createcluster/base/pgcluster.yaml new file mode 100644 index 0000000000..29aa0c6e83 --- /dev/null +++ b/examples/kustomize/createcluster/base/pgcluster.yaml @@ -0,0 +1,105 @@ +apiVersion: crunchydata.com/v1 +kind: Pgcluster +metadata: + annotations: + current-primary: hippo + labels: + autofail: "true" + crunchy-pgbadger: "false" + crunchy-pgha-scope: hippo + crunchy-postgres-exporter: "false" + deployment-name: hippo + name: hippo + pg-cluster: hippo + pg-pod-anti-affinity: "" + pgo-backrest: "true" + pgo-version: 4.5.1 + pgouser: admin + name: hippo + namespace: pgo +spec: + BackrestStorage: + accessmode: ReadWriteOnce + matchLabels: "" + name: "" + size: 1G + storageclass: "" + storagetype: dynamic + supplementalgroups: "" + PrimaryStorage: + accessmode: ReadWriteOnce + matchLabels: "" + name: hippo + size: 1G + storageclass: "" + storagetype: dynamic + supplementalgroups: "" + ReplicaStorage: + accessmode: ReadWriteOnce + matchLabels: "" + name: "" + size: 1G + storageclass: "" + storagetype: dynamic + supplementalgroups: "" + annotations: + global: + favorite: "" + backrest: + chair: "" + pgBouncer: + pool: "" + postgres: + elephant: "" + backrestLimits: {} + backrestRepoPath: "" + backrestResources: + memory: 48Mi + backrestS3Bucket: "" + backrestS3Endpoint: "" + backrestS3Region: "" + backrestS3URIStyle: "" + backrestS3VerifyTLS: "" + ccpimage: crunchy-postgres-ha + ccpimageprefix: registry.developers.crunchydata.com/crunchydata + ccpimagetag: centos7-12.5-4.5.1 + clustername: hippo + customconfig: "" + database: hippo + exporterport: "9187" + limits: {} + name: hippo + namespace: pgo + pgBouncer: + limits: {} + replicas: 0 + resources: + memory: "0" + pgDataSource: + restoreFrom: "" + restoreOpts: "" + pgbadgerport: "10000" + pgoimageprefix: registry.developers.crunchydata.com/crunchydata + podAntiAffinity: + default: preferred + pgBackRest: preferred + pgBouncer: preferred + policies: "" + port: "5432" + primarysecretname: hippo-primaryuser-secret + replicas: "0" + rootsecretname: hippo-postgres-secret + shutdown: false + standby: false + tablespaceMounts: {} + tls: + caSecret: "" + replicationTLSSecret: "" + tlsSecret: "" + tlsOnly: false + user: hippo + userlabels: + crunchy-postgres-exporter: "false" + pg-pod-anti-affinity: "" + pgo-version: 4.5.1 + usersecretname: hippo-hippo-secret diff --git a/examples/kustomize/createcluster/overlay/dev/bouncer.json b/examples/kustomize/createcluster/overlay/dev/bouncer.json new file mode 100644 index 0000000000..622283f1fe --- /dev/null +++ b/examples/kustomize/createcluster/overlay/dev/bouncer.json @@ -0,0 +1,4 @@ +[ + { "op": "add", "path": "/spec/pgBouncer/resources/memory", "value": "24Mi"}, + { "op": "add", "path": "/spec/pgBouncer/replicas", "value": 1 } +] \ No newline at end of file diff --git a/examples/kustomize/createcluster/overlay/dev/devhippo.json b/examples/kustomize/createcluster/overlay/dev/devhippo.json new file mode 100644 index 0000000000..ab7c2e5071 --- /dev/null +++ b/examples/kustomize/createcluster/overlay/dev/devhippo.json @@ -0,0 +1,18 @@ +[ + { "op": "replace", "path": "/metadata/annotations/current-primary", "value": "dev-hippo" }, + { "op": "replace", "path": "/metadata/labels/crunchy-pgha-scope", "value": "dev-hippo" }, + { "op": "replace", "path": "/metadata/labels/deployment-name", "value": "dev-hippo" }, + { "op": "replace", "path": "/metadata/labels/name", "value": "dev-hippo" }, + { "op": "replace", "path": "/metadata/labels/pg-cluster", "value": "dev-hippo" }, + { "op": "replace", "path": "/metadata/name", "value": "dev-hippo" }, + + { "op": "replace", "path": "/spec/PrimaryStorage/name", "value": "dev-hippo" }, + { "op": "replace", "path": "/spec/clustername", "value": "dev-hippo" }, + { "op": "replace", "path": "/spec/PrimaryStorage/name", "value": "dev-hippo" }, + { "op": "replace", "path": "/spec/clustername", "value": "dev-hippo" }, + { "op": "replace", "path": "/spec/database", "value": "dev-hippo" }, + { "op": "replace", "path": "/spec/name", "value": "dev-hippo" }, + { "op": "replace", "path": "/spec/primarysecretname", "value": "dev-hippo-primaryuser-secret" }, + { "op": "replace", "path": "/spec/rootsecretname", "value": "dev-hippo-postgres-secret" }, + { "op": "replace", "path": "/spec/usersecretname", "value": "dev-hippo-hippo-secret" } +] \ No newline at end of file diff --git a/examples/kustomize/createcluster/overlay/dev/kustomization.yaml b/examples/kustomize/createcluster/overlay/dev/kustomization.yaml new file mode 100644 index 0000000000..a78fe401af --- /dev/null +++ b/examples/kustomize/createcluster/overlay/dev/kustomization.yaml @@ -0,0 +1,22 @@ +resources: +- ../../base +namePrefix: dev- +namespace: pgo +commonLabels: + environment: development + +patchesJson6902: + - target: + group: crunchydata.com + version: v1 + namespace: pgo + kind: Pgcluster + name: dev-hippo + path: devhippo.json + - target: + group: crunchydata.com + version: v1 + namespace: pgo + kind: Pgcluster + name: dev-hippo + path: bouncer.json \ No newline at end of file diff --git a/examples/kustomize/createcluster/overlay/prod/kustomization.yaml b/examples/kustomize/createcluster/overlay/prod/kustomization.yaml new file mode 100644 index 0000000000..76e5756697 --- /dev/null +++ b/examples/kustomize/createcluster/overlay/prod/kustomization.yaml @@ -0,0 +1,15 @@ +resources: +- ../../base +namePrefix: prod- +namespace: pgo +commonLabels: + environment: production + +patchesJson6902: + - target: + group: crunchydata.com + version: v1 + namespace: pgo + kind: Pgcluster + name: prod-hippo + path: prodhippo.json \ No newline at end of file diff --git a/examples/kustomize/createcluster/overlay/prod/prodhippo.json b/examples/kustomize/createcluster/overlay/prod/prodhippo.json new file mode 100644 index 0000000000..ef8313629d --- /dev/null +++ b/examples/kustomize/createcluster/overlay/prod/prodhippo.json @@ -0,0 +1,19 @@ +[ + { "op": "replace", "path": "/metadata/annotations/current-primary", "value": "prod-hippo" }, + { "op": "replace", "path": "/metadata/labels/crunchy-pgha-scope", "value": "prod-hippo" }, + { "op": "replace", "path": "/metadata/labels/deployment-name", "value": "prod-hippo" }, + { "op": "replace", "path": "/metadata/labels/name", "value": "prod-hippo" }, + { "op": "replace", "path": "/metadata/labels/pg-cluster", "value": "prod-hippo" }, + { "op": "replace", "path": "/metadata/name", "value": "prod-hippo" }, + + { "op": "replace", "path": "/spec/PrimaryStorage/name", "value": "prod-hippo" }, + { "op": "replace", "path": "/spec/clustername", "value": "prod-hippo" }, + { "op": "replace", "path": "/spec/PrimaryStorage/name", "value": "prod-hippo" }, + { "op": "replace", "path": "/spec/clustername", "value": "prod-hippo" }, + { "op": "replace", "path": "/spec/database", "value": "prod-hippo" }, + { "op": "replace", "path": "/spec/name", "value": "prod-hippo" }, + { "op": "replace", "path": "/spec/primarysecretname", "value": "prod-hippo-primaryuser-secret" }, + { "op": "replace", "path": "/spec/replicas", "value": "1"}, + { "op": "replace", "path": "/spec/rootsecretname", "value": "prod-hippo-postgres-secret" }, + { "op": "replace", "path": "/spec/usersecretname", "value": "prod-hippo-hippo-secret" } +] \ No newline at end of file diff --git a/examples/kustomize/createcluster/overlay/staging/annotations.json b/examples/kustomize/createcluster/overlay/staging/annotations.json new file mode 100644 index 0000000000..34983a01c7 --- /dev/null +++ b/examples/kustomize/createcluster/overlay/staging/annotations.json @@ -0,0 +1,6 @@ +[ + { "op": "add", "path": "/spec/annotations/global/favorite", "value": "hippo"}, + { "op": "add", "path": "/spec/annotations/backrest/chair", "value": "comfy"}, + { "op": "add", "path": "/spec/annotations/pgBouncer/pool", "value": "swimming"}, + { "op": "add", "path": "/spec/annotations/postgres/elephant", "value": "cool"} +] \ No newline at end of file diff --git a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml new file mode 100644 index 0000000000..33a36b5ef9 --- /dev/null +++ b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml @@ -0,0 +1,27 @@ +apiVersion: crunchydata.com/v1 +kind: Pgreplica +metadata: + labels: + name: staging-hippo-rpl1 + pg-cluster: staging-hippo + pgouser: admin + name: hippo-rpl1 + namespace: pgo +spec: + clustername: staging-hippo + name: staging-hippo-rpl1 + namespace: pgo + replicastorage: + accessmode: ReadWriteOnce + matchLabels: "" + name: staging-hippo-rpl1 + size: 2G + storageclass: "" + storagetype: dynamic + supplementalgroups: "" + userlabels: + NodeLabelKey: "" + NodeLabelValue: "" + crunchy-postgres-exporter: "false" + pg-pod-anti-affinity: "" + pgo-version: 4.5.1 diff --git a/examples/kustomize/createcluster/overlay/staging/kustomization.yaml b/examples/kustomize/createcluster/overlay/staging/kustomization.yaml new file mode 100644 index 0000000000..4fb92b8d16 --- /dev/null +++ b/examples/kustomize/createcluster/overlay/staging/kustomization.yaml @@ -0,0 +1,23 @@ +resources: +- ../../base +- hippo-rpl1-pgreplica.yaml +namePrefix: staging- +namespace: pgo +commonLabels: + environment: staging + +patchesJson6902: + - target: + group: crunchydata.com + version: v1 + namespace: pgo + kind: Pgcluster + name: staging-hippo + path: staginghippo.json + - target: + group: crunchydata.com + version: v1 + namespace: pgo + kind: Pgcluster + name: staging-hippo + path: annotations.json \ No newline at end of file diff --git a/examples/kustomize/createcluster/overlay/staging/staginghippo.json b/examples/kustomize/createcluster/overlay/staging/staginghippo.json new file mode 100644 index 0000000000..c19acb5895 --- /dev/null +++ b/examples/kustomize/createcluster/overlay/staging/staginghippo.json @@ -0,0 +1,19 @@ +[ + { "op": "replace", "path": "/metadata/annotations/current-primary", "value": "staging-hippo" }, + { "op": "replace", "path": "/metadata/labels/crunchy-pgha-scope", "value": "staging-hippo" }, + { "op": "replace", "path": "/metadata/labels/deployment-name", "value": "staging-hippo" }, + { "op": "replace", "path": "/metadata/labels/name", "value": "staging-hippo" }, + { "op": "replace", "path": "/metadata/labels/pg-cluster", "value": "staging-hippo" }, + { "op": "replace", "path": "/metadata/name", "value": "staging-hippo" }, + + { "op": "replace", "path": "/spec/PrimaryStorage/name", "value": "staging-hippo" }, + { "op": "replace", "path": "/spec/clustername", "value": "staging-hippo" }, + { "op": "replace", "path": "/spec/PrimaryStorage/name", "value": "staging-hippo" }, + { "op": "replace", "path": "/spec/clustername", "value": "staging-hippo" }, + { "op": "replace", "path": "/spec/database", "value": "staging-hippo" }, + { "op": "replace", "path": "/spec/name", "value": "staging-hippo" }, + { "op": "replace", "path": "/spec/primarysecretname", "value": "staging-hippo-primaryuser-secret" }, + { "op": "replace", "path": "/spec/replicas", "value": "1"}, + { "op": "replace", "path": "/spec/rootsecretname", "value": "staging-hippo-postgres-secret" }, + { "op": "replace", "path": "/spec/usersecretname", "value": "staging-hippo-hippo-secret" } +] \ No newline at end of file From c18b29ec476b1353d4351addd0c86fd2103fa9cc Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 20 Dec 2020 11:37:57 -0500 Subject: [PATCH 057/276] Add missing `--no-prompt` flag to `pgo upgrade` The mechanism for disabling the verification prompt for `pgo upgrade` was always available, but the flag itself was not exposed. Issue: [ch9988] Issue: #2135 --- cmd/pgo/cmd/upgrade.go | 11 +++++------ docs/content/pgo-client/reference/pgo_upgrade.md | 11 ++++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cmd/pgo/cmd/upgrade.go b/cmd/pgo/cmd/upgrade.go index 0e734ade5e..31122cebf7 100644 --- a/cmd/pgo/cmd/upgrade.go +++ b/cmd/pgo/cmd/upgrade.go @@ -41,10 +41,10 @@ var UpgradeCCPImageTag string var UpgradeCmd = &cobra.Command{ Use: "upgrade", Short: "Perform a cluster upgrade.", - Long: `UPGRADE allows you to perform a comprehensive PGCluster upgrade - (for use after performing a Postgres Operator upgrade). + Long: `UPGRADE allows you to perform a comprehensive PGCluster upgrade + (for use after performing a Postgres Operator upgrade). For example: - + pgo upgrade mycluster Upgrades the cluster for use with the upgraded Postgres Operator version.`, Run: func(cmd *cobra.Command, args []string) { @@ -69,8 +69,9 @@ func init() { RootCmd.AddCommand(UpgradeCmd) // flags for "pgo upgrade" - UpgradeCmd.Flags().BoolVarP(&IgnoreValidation, "ignore-validation", "", false, "Disables version checking against the image tags when performing an cluster upgrade.") UpgradeCmd.Flags().StringVarP(&UpgradeCCPImageTag, "ccp-image-tag", "", "", "The image tag to use for cluster creation. If specified, it overrides the default configuration setting and disables tag validation checking.") + UpgradeCmd.Flags().BoolVarP(&IgnoreValidation, "ignore-validation", "", false, "Disables version checking against the image tags when performing an cluster upgrade.") + UpgradeCmd.Flags().BoolVar(&NoPrompt, "no-prompt", false, "No command line confirmation.") } func createUpgrade(args []string, ns string) { @@ -90,7 +91,6 @@ func createUpgrade(args []string, ns string) { request.UpgradeCCPImageTag = UpgradeCCPImageTag response, err := api.CreateUpgrade(httpclient, &SessionCredentials, &request) - if err != nil { fmt.Println("Error: " + err.Error()) os.Exit(2) @@ -104,5 +104,4 @@ func createUpgrade(args []string, ns string) { fmt.Println("Error: " + response.Status.Msg) os.Exit(2) } - } diff --git a/docs/content/pgo-client/reference/pgo_upgrade.md b/docs/content/pgo-client/reference/pgo_upgrade.md index 78d787f6f0..534790f189 100644 --- a/docs/content/pgo-client/reference/pgo_upgrade.md +++ b/docs/content/pgo-client/reference/pgo_upgrade.md @@ -7,10 +7,10 @@ Perform a cluster upgrade. ### Synopsis -UPGRADE allows you to perform a comprehensive PGCluster upgrade - (for use after performing a Postgres Operator upgrade). +UPGRADE allows you to perform a comprehensive PGCluster upgrade + (for use after performing a Postgres Operator upgrade). For example: - + pgo upgrade mycluster Upgrades the cluster for use with the upgraded Postgres Operator version. @@ -24,12 +24,13 @@ pgo upgrade [flags] --ccp-image-tag string The image tag to use for cluster creation. If specified, it overrides the default configuration setting and disables tag validation checking. -h, --help help for upgrade --ignore-validation Disables version checking against the image tags when performing an cluster upgrade. + --no-prompt No command line confirmation. ``` ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -43,4 +44,4 @@ pgo upgrade [flags] * [pgo](/pgo-client/reference/pgo/) - The pgo command line interface. -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 20-Dec-2020 From 86d327abb28aaf9281e67403a22948ca691f9bbe Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 20 Dec 2020 12:30:26 -0500 Subject: [PATCH 058/276] Ensure consistent permissions for mounting repo Secret While the Secret volume mount is set to be readonly for the pgBackRest Secret information, the defaultMode on the volume itself was set to be more permissive. While it appears that the vast majority of Kubernetes distributions gie precedence to the value of the volume mount, so flavors do use the values set on the volume. As such, it's prudent to remove the more permissive settings, which this patch does. Issue: [ch9989] Issue: #2140 --- .../pgo-operator/files/pgo-configs/cluster-bootstrap-job.json | 3 +-- .../pgo-operator/files/pgo-configs/cluster-deployment.json | 3 +-- .../files/pgo-configs/pgo-backrest-repo-template.json | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json index ecd2cf735a..9bd5a10f21 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json @@ -166,8 +166,7 @@ }, { "name": "sshd", "secret": { - "secretName": "{{.RestoreFrom}}-backrest-repo-config", - "defaultMode": 511 + "secretName": "{{.RestoreFrom}}-backrest-repo-config" } }, {{if .TLSEnabled}} diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json index 0081bb205f..c05ee7210c 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json @@ -259,8 +259,7 @@ }, { "name": "sshd", "secret": { - "secretName": "{{.ClusterName}}-backrest-repo-config", - "defaultMode": 511 + "secretName": "{{.ClusterName}}-backrest-repo-config" } }, { "name": "root-volume", diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json index dba4a3d8d7..30b69dc4b6 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json @@ -102,8 +102,7 @@ "volumes": [{ "name": "sshd", "secret": { - "secretName": "{{.SshdSecretsName}}", - "defaultMode": 511 + "secretName": "{{.SshdSecretsName}}" } }, { "name": "backrestrepo", From 73b5b01864153f357d4880542f18d88cc62bdb1d Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 20 Dec 2020 16:29:57 -0500 Subject: [PATCH 059/276] Allow for explicit deletion of pgBackRest backups This introduces the ability to delete pgBackRest backups using the `pgo delete backup` command. The pgBackRest backup name must be specified using the `--target` command, which can be determined through a call to `pgo show backup`. This also includes obligatory language on when to use explicit backup deleting to ensure the user does not take actions on their pgBackRest repository that they do not intend to. Issue: #2111 --- cmd/pgo/api/backrest.go | 46 ++++++++- cmd/pgo/cmd/backup.go | 33 ++++++- cmd/pgo/cmd/delete.go | 46 +++++---- .../content/architecture/disaster-recovery.md | 93 ++++++++++++++++++ .../pgo-client/reference/pgo_delete.md | 6 +- .../pgo-client/reference/pgo_delete_backup.md | 12 ++- docs/content/tutorial/disaster-recovery.md | 86 +++++++++++++++++ .../apiserver/backrestservice/backrestimpl.go | 95 ++++++++++++++----- .../backrestservice/backrestservice.go | 85 ++++++++++++++++- internal/apiserver/routing/routes.go | 1 + pkg/apiservermsgs/backrestmsgs.go | 21 ++++ 11 files changed, 466 insertions(+), 58 deletions(-) diff --git a/cmd/pgo/api/backrest.go b/cmd/pgo/api/backrest.go index 13e2ff702b..e0e087efbf 100644 --- a/cmd/pgo/api/backrest.go +++ b/cmd/pgo/api/backrest.go @@ -25,8 +25,50 @@ import ( log "github.com/sirupsen/logrus" ) -func ShowBackrest(httpclient *http.Client, arg, selector string, SessionCredentials *msgs.BasicAuthCredentials, ns string) (msgs.ShowBackrestResponse, error) { +// DeleteBackup makes an API requests to delete a pgBackRest backup +func DeleteBackup(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request msgs.DeleteBackrestBackupRequest) (msgs.DeleteBackrestBackupResponse, error) { + var response msgs.DeleteBackrestBackupResponse + + // explicitly set the client version here + request.ClientVersion = msgs.PGO_VERSION + + log.Debugf("DeleteBackup called [%+v]", request) + + jsonValue, _ := json.Marshal(request) + url := SessionCredentials.APIServerURL + "/backrest" + action := "DELETE" + req, err := http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) + if err != nil { + return response, err + } + + req.Header.Set("Content-Type", "application/json") + req.SetBasicAuth(SessionCredentials.Username, SessionCredentials.Password) + + resp, err := httpclient.Do(req) + if err != nil { + return response, err + } + + defer resp.Body.Close() + + log.Debugf("%+v", resp) + + if err := StatusCheck(resp); err != nil { + return response, err + } + + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + fmt.Print("Error: ") + fmt.Println(err) + return response, err + } + + return response, nil +} + +func ShowBackrest(httpclient *http.Client, arg, selector string, SessionCredentials *msgs.BasicAuthCredentials, ns string) (msgs.ShowBackrestResponse, error) { var response msgs.ShowBackrestResponse url := SessionCredentials.APIServerURL + "/backrest/" + arg + "?version=" + msgs.PGO_VERSION + "&selector=" + selector + "&namespace=" + ns @@ -58,11 +100,9 @@ func ShowBackrest(httpclient *http.Client, arg, selector string, SessionCredenti } return response, err - } func CreateBackrestBackup(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.CreateBackrestBackupRequest) (msgs.CreateBackrestBackupResponse, error) { - var response msgs.CreateBackrestBackupResponse jsonValue, _ := json.Marshal(request) diff --git a/cmd/pgo/cmd/backup.go b/cmd/pgo/cmd/backup.go index 61225cc3ea..d70877a198 100644 --- a/cmd/pgo/cmd/backup.go +++ b/cmd/pgo/cmd/backup.go @@ -18,8 +18,12 @@ package cmd import ( "fmt" + "os" + "github.com/crunchydata/postgres-operator/cmd/pgo/api" "github.com/crunchydata/postgres-operator/internal/config" + msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -74,7 +78,6 @@ var backupCmd = &cobra.Command{ } } - }, } @@ -89,10 +92,32 @@ func init() { backupCmd.Flags().StringVarP(&PGDumpDB, "database", "d", "postgres", "The name of the database pgdump will backup.") backupCmd.Flags().StringVar(&backupType, "backup-type", "pgbackrest", "The backup type to perform. Default is pgbackrest. Valid backup types are pgbackrest and pgdump.") backupCmd.Flags().StringVarP(&BackrestStorageType, "pgbackrest-storage-type", "", "", "The type of storage to use when scheduling pgBackRest backups. Either \"local\", \"s3\" or both, comma separated. (default \"local\")") - } // deleteBackup .... -func deleteBackup(args []string, ns string) { - log.Debugf("deleteBackup called %v", args) +func deleteBackup(namespace, clusterName string) { + request := msgs.DeleteBackrestBackupRequest{ + ClusterName: clusterName, + Namespace: namespace, + Target: Target, + } + + // make the request + response, err := api.DeleteBackup(httpclient, &SessionCredentials, request) + + // if everything is OK, exit early + if err == nil && response.Status.Code == msgs.Ok { + return + } + + // if there is an error, or the response code is not ok, print the error and + // exit + if err != nil { + fmt.Println("Error: " + err.Error()) + } else if response.Status.Code == msgs.Error { + fmt.Println("Error: " + response.Status.Msg) + } + + // since we can only have errors at this point, exit with error + os.Exit(1) } diff --git a/cmd/pgo/cmd/delete.go b/cmd/pgo/cmd/delete.go index c4dce568a6..df21d71ea4 100644 --- a/cmd/pgo/cmd/delete.go +++ b/cmd/pgo/cmd/delete.go @@ -17,6 +17,7 @@ package cmd import ( "fmt" + "os" "github.com/crunchydata/postgres-operator/cmd/pgo/util" "github.com/spf13/cobra" @@ -36,7 +37,7 @@ var deleteCmd = &cobra.Command{ Short: "Delete an Operator resource", Long: `The delete command allows you to delete an Operator resource. For example: - pgo delete backup mycluster + pgo delete backup mycluster --target=backup-name pgo delete cluster mycluster pgo delete cluster mycluster --delete-data pgo delete cluster mycluster --delete-data --delete-backups @@ -53,7 +54,6 @@ var deleteCmd = &cobra.Command{ pgo delete schedule mycluster pgo delete user --username=testuser --selector=name=mycluster`, Run: func(cmd *cobra.Command, args []string) { - if len(args) == 0 { fmt.Println(`Error: You must specify the type of resource to delete. Valid resource types include: * backup @@ -94,7 +94,6 @@ var deleteCmd = &cobra.Command{ * user`) } } - }, } @@ -118,6 +117,13 @@ func init() { // "pgo delete backup" // used to delete backups deleteCmd.AddCommand(deleteBackupCmd) + // "pgo delete backup --no-prompt" + // disables the verification prompt for deleting a backup + deleteBackupCmd.Flags().BoolVar(&NoPrompt, "no-prompt", false, "No command line confirmation.") + // "pgo delete backup --target" + // the backup target to expire + deleteBackupCmd.Flags().StringVar(&Target, "target", "", "The backup to expire, e.g. "+ + "\"20201220-171801F\". Use \"pgo show backup\" to determine the target.") // "pgo delete cluster" // used to delete clusters @@ -294,22 +300,30 @@ func init() { var deleteBackupCmd = &cobra.Command{ Use: "backup", Short: "Delete a backup", - Long: `Delete a backup. For example: + Long: `Delete a backup from pgBackRest. Requires a target backup. For example: - pgo delete backup mydatabase`, + pgo delete backup hippo --target=20201220-171801F`, Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + fmt.Println("Error: A cluster name is required for this command.") + os.Exit(1) + } + + if Target == "" { + fmt.Println("Error: --target must be specified.") + os.Exit(1) + } + + if !util.AskForConfirmation(NoPrompt, "If you delete a backup that is *not* set to expire, you may be unable to meet your retention requirements. Proceed?") { + fmt.Println("Aborting...") + return + } + if Namespace == "" { Namespace = PGONamespace } - if len(args) == 0 { - fmt.Println("Error: A database or cluster name is required for this command.") - } else { - if util.AskForConfirmation(NoPrompt, "") { - deleteBackup(args, Namespace) - } else { - fmt.Println("Aborting...") - } - } + + deleteBackup(Namespace, args[0]) }, } @@ -453,7 +467,6 @@ var deletePgAdminCmd = &cobra.Command{ } else { if util.AskForConfirmation(NoPrompt, "") { deletePgAdmin(args, Namespace) - } else { fmt.Println("Aborting...") } @@ -477,7 +490,6 @@ var deletePgbouncerCmd = &cobra.Command{ } else { if util.AskForConfirmation(NoPrompt, "") { deletePgbouncer(args, Namespace) - } else { fmt.Println("Aborting...") } @@ -542,7 +554,6 @@ var deleteUserCmd = &cobra.Command{ pgo delete user --username=someuser --selector=name=mycluster`, Run: func(cmd *cobra.Command, args []string) { - if Namespace == "" { Namespace = PGONamespace } @@ -551,7 +562,6 @@ var deleteUserCmd = &cobra.Command{ } else { if util.AskForConfirmation(NoPrompt, "") { deleteUser(args, Namespace) - } else { fmt.Println("Aborting...") } diff --git a/docs/content/architecture/disaster-recovery.md b/docs/content/architecture/disaster-recovery.md index 161bcedac0..7d3639e0fc 100644 --- a/docs/content/architecture/disaster-recovery.md +++ b/docs/content/architecture/disaster-recovery.md @@ -304,3 +304,96 @@ To enable a PostgreSQL cluster to use S3, the `--pgbackrest-storage-type` on the Once configured, the `pgo backup` and `pgo restore` commands will work with S3 similarly to the above! + +## Deleting a Backup + +{{% notice warning %}} +If you delete a backup that is *not* set to expire, you may be unable to meet +your retention requirements. If you are deleting backups to free space, it is +recommended to delete your oldest backups first. +{{% /notice %}} + +A backup can be deleted using the [`pgo delete backup`]({{< relref "pgo-client/reference/pgo_delete_backup.md" >}}) +command. You must specify a specific backup to delete using the `--target` flag. +You can get the backup names from the +[`pgo show backup`]({{< relref "pgo-client/reference/pgo_show_backup.md" >}}) +command. + +For example, using a PostgreSQL cluster called `hippo`, pretend there is an +example pgBackRest repository in the state shown after running the + `pgo show backup hippo` command: + +``` +cluster: hippo +storage type: local + +stanza: db + status: ok + cipher: none + + db (current) + wal archive min/max (12-1) + + full backup: 20201220-171801F + timestamp start/stop: 2020-12-20 17:18:01 +0000 UTC / 2020-12-20 17:18:10 +0000 UTC + wal start/stop: 000000010000000000000002 / 000000010000000000000002 + database size: 31.3MiB, backup size: 31.3MiB + repository size: 3.8MiB, repository backup size: 3.8MiB + backup reference list: + + incr backup: 20201220-171801F_20201220-171939I + timestamp start/stop: 2020-12-20 17:19:39 +0000 UTC / 2020-12-20 17:19:41 +0000 UTC + wal start/stop: 000000010000000000000005 / 000000010000000000000005 + database size: 31.3MiB, backup size: 216.3KiB + repository size: 3.8MiB, repository backup size: 25.9KiB + backup reference list: 20201220-171801F + + incr backup: 20201220-171801F_20201220-172046I + timestamp start/stop: 2020-12-20 17:20:46 +0000 UTC / 2020-12-20 17:23:29 +0000 UTC + wal start/stop: 00000001000000000000000A / 00000001000000000000000A + database size: 65.9MiB, backup size: 37.5MiB + repository size: 7.7MiB, repository backup size: 4.3MiB + backup reference list: 20201220-171801F, 20201220-171801F_20201220-171939I + + full backup: 20201220-201305F + timestamp start/stop: 2020-12-20 20:13:05 +0000 UTC / 2020-12-20 20:13:15 +0000 UTC + wal start/stop: 00000001000000000000000F / 00000001000000000000000F + database size: 65.9MiB, backup size: 65.9MiB + repository size: 7.7MiB, repository backup size: 7.7MiB + backup reference list: +``` + +The backup targets can be found after the backup type, e.g. `20201220-171801F` +or `20201220-171801F_20201220-172046I`. + +One can delete the oldest backup, in this case `20201220-171801F`, by running +the following command: + +``` +pgo delete backup hippo --target=20201220-171801F +``` + +Verify the backup is deleted with `pgo show backup hippo`: + +``` +cluster: hippo +storage type: local + +stanza: db + status: ok + cipher: none + + db (current) + wal archive min/max (12-1) + + full backup: 20201220-201305F + timestamp start/stop: 2020-12-20 20:13:05 +0000 UTC / 2020-12-20 20:13:15 +0000 UTC + wal start/stop: 00000001000000000000000F / 00000001000000000000000F + database size: 65.9MiB, backup size: 65.9MiB + repository size: 7.7MiB, repository backup size: 7.7MiB + backup reference list: +``` + +(Note: this had the net effect of expiring all of the incremental backups +associated with the full backup that as deleted. This is a feature of +pgBackRest). diff --git a/docs/content/pgo-client/reference/pgo_delete.md b/docs/content/pgo-client/reference/pgo_delete.md index b2233b9c50..ea25f615b0 100644 --- a/docs/content/pgo-client/reference/pgo_delete.md +++ b/docs/content/pgo-client/reference/pgo_delete.md @@ -9,7 +9,7 @@ Delete an Operator resource The delete command allows you to delete an Operator resource. For example: - pgo delete backup mycluster + pgo delete backup mycluster --target=backup-name pgo delete cluster mycluster pgo delete cluster mycluster --delete-data pgo delete cluster mycluster --delete-data --delete-backups @@ -39,7 +39,7 @@ pgo delete [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -64,4 +64,4 @@ pgo delete [flags] * [pgo delete schedule](/pgo-client/reference/pgo_delete_schedule/) - Delete a schedule * [pgo delete user](/pgo-client/reference/pgo_delete_user/) - Delete a user -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 20-Dec-2020 diff --git a/docs/content/pgo-client/reference/pgo_delete_backup.md b/docs/content/pgo-client/reference/pgo_delete_backup.md index 22bf95e3c9..67b40d1c53 100644 --- a/docs/content/pgo-client/reference/pgo_delete_backup.md +++ b/docs/content/pgo-client/reference/pgo_delete_backup.md @@ -7,9 +7,9 @@ Delete a backup ### Synopsis -Delete a backup. For example: +Delete a backup from pgBackRest. Requires a target backup. For example: - pgo delete backup mydatabase + pgo delete backup hippo --target=20201220-171801F ``` pgo delete backup [flags] @@ -18,13 +18,15 @@ pgo delete backup [flags] ### Options ``` - -h, --help help for backup + -h, --help help for backup + --no-prompt No command line confirmation. + --target string The backup to expire, e.g. "20201220-171801F". Use "pgo show backup" to determine the target. ``` ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -38,4 +40,4 @@ pgo delete backup [flags] * [pgo delete](/pgo-client/reference/pgo_delete/) - Delete an Operator resource -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 20-Dec-2020 diff --git a/docs/content/tutorial/disaster-recovery.md b/docs/content/tutorial/disaster-recovery.md index ca05361674..0ad19d0fe3 100644 --- a/docs/content/tutorial/disaster-recovery.md +++ b/docs/content/tutorial/disaster-recovery.md @@ -183,6 +183,92 @@ When the restore is complete, the cluster is immediately available for reads and The PostgreSQL Operator supports the full set of pgBackRest restore options, which can be passed into the `--backup-opts` parameter. For more information, please review the [pgBackRest restore options](https://pgbackrest.org/command.html#command-restore) +## Deleting a Backup + +You typically do not want to delete backups. Instead, it's better to set a backup retention policy as part of [scheduling your ackups](#schedule-backups). + +However, there are situations where you may want to explicitly delete backups, in particular, if you need to reclaim space on your backup disk or if you accidentally created too many backups. + +{{% notice warning %}} +If you delete a backup that is *not* set to expire, you may be unable to meet your retention requirements. If you are deleting backups to free space, it is recommended to delete your oldest backups first. +{{% /notice %}} + +In these cases, a backup can be deleted using the [`pgo delete backup`]({{< relref "pgo-client/reference/pgo_delete_backup.md" >}}) +command. You must specify a specific backup to delete using the `--target` flag. You can get the backup names from the [`pgo show backup`]({{< relref "pgo-client/reference/pgo_show_backup.md" >}}) command. + +Let's say that the `hippo` cluster currently has a set of backups that look like this, obtained from running the `pgo show backup hippo` command: + +``` +cluster: hippo +storage type: local + +stanza: db + status: ok + cipher: none + + db (current) + wal archive min/max (12-1) + + full backup: 20201220-171801F + timestamp start/stop: 2020-12-20 17:18:01 +0000 UTC / 2020-12-20 17:18:10 +0000 UTC + wal start/stop: 000000010000000000000002 / 000000010000000000000002 + database size: 31.3MiB, backup size: 31.3MiB + repository size: 3.8MiB, repository backup size: 3.8MiB + backup reference list: + + incr backup: 20201220-171801F_20201220-171939I + timestamp start/stop: 2020-12-20 17:19:39 +0000 UTC / 2020-12-20 17:19:41 +0000 UTC + wal start/stop: 000000010000000000000005 / 000000010000000000000005 + database size: 31.3MiB, backup size: 216.3KiB + repository size: 3.8MiB, repository backup size: 25.9KiB + backup reference list: 20201220-171801F + + incr backup: 20201220-171801F_20201220-172046I + timestamp start/stop: 2020-12-20 17:20:46 +0000 UTC / 2020-12-20 17:23:29 +0000 UTC + wal start/stop: 00000001000000000000000A / 00000001000000000000000A + database size: 65.9MiB, backup size: 37.5MiB + repository size: 7.7MiB, repository backup size: 4.3MiB + backup reference list: 20201220-171801F, 20201220-171801F_20201220-171939I + + full backup: 20201220-201305F + timestamp start/stop: 2020-12-20 20:13:05 +0000 UTC / 2020-12-20 20:13:15 +0000 UTC + wal start/stop: 00000001000000000000000F / 00000001000000000000000F + database size: 65.9MiB, backup size: 65.9MiB + repository size: 7.7MiB, repository backup size: 7.7MiB + backup reference list: +``` + +Note that the backup targets can be found after the backup type, e.g. `20201220-171801F` or `20201220-171801F_20201220-172046I`. + +One can delete the oldest backup, in this case `20201220-171801F`, by running the following command: + +``` +pgo delete backup hippo --target=20201220-171801F +``` + +You can then verify the backup is deleted with `pgo show backup hippo`: + +``` +cluster: hippo +storage type: local + +stanza: db + status: ok + cipher: none + + db (current) + wal archive min/max (12-1) + + full backup: 20201220-201305F + timestamp start/stop: 2020-12-20 20:13:05 +0000 UTC / 2020-12-20 20:13:15 +0000 UTC + wal start/stop: 00000001000000000000000F / 00000001000000000000000F + database size: 65.9MiB, backup size: 65.9MiB + repository size: 7.7MiB, repository backup size: 7.7MiB + backup reference list: +``` + +Note that deleting the oldest backup also had the effect of deleting all of the backups that depended on it. This is a feature of [pgBackRest](https://pgbackrest.org/)! + ## Next Steps There are cases where you may want to take [logical backups]({{< relref "tutorial/pgdump.md" >}}), aka `pg_dump` / `pg_dumpall`. Let's learn how to do that with the PostgreSQL Operator! diff --git a/internal/apiserver/backrestservice/backrestimpl.go b/internal/apiserver/backrestservice/backrestimpl.go index 3206d7fcee..7acc51d052 100644 --- a/internal/apiserver/backrestservice/backrestimpl.go +++ b/internal/apiserver/backrestservice/backrestimpl.go @@ -42,9 +42,15 @@ import ( const containername = "database" -// pgBackRestInfoCommand is the baseline command used for getting the -// pgBackRest info -var pgBackRestInfoCommand = []string{"pgbackrest", "info", "--output", "json"} +var ( + // pgBackRestExpireCommand is the baseline command used for deleting a + // pgBackRest backup + pgBackRestExpireCommand = []string{"pgbackrest", "expire", "--set"} + + // pgBackRestInfoCommand is the baseline command used for getting the + // pgBackRest info + pgBackRestInfoCommand = []string{"pgbackrest", "info", "--output", "json"} +) // repoTypeFlagS3 is used for getting the pgBackRest info for a repository that // is stored in S3 @@ -76,7 +82,7 @@ func CreateBackup(request *msgs.CreateBackrestBackupRequest, ns, pgouser string) clusterList := crv1.PgclusterList{} var err error if request.Selector != "" { - //use the selector instead of an argument list to filter on + // use the selector instead of an argument list to filter on cl, err := apiserver.Clientset. CrunchydataV1().Pgclusters(ns). List(ctx, metav1.ListOptions{LabelSelector: request.Selector}) @@ -165,9 +171,7 @@ func CreateBackup(request *msgs.CreateBackrestBackupRequest, ns, pgouser string) return resp } else { - //remove any previous backup job - - //selector := config.LABEL_PG_CLUSTER + "=" + clusterName + "," + config.LABEL_BACKREST + "=true" + // remove any previous backup job selector := config.LABEL_BACKREST_COMMAND + "=" + crv1.PgtaskBackrestBackup + "," + config.LABEL_PG_CLUSTER + "=" + clusterName + "," + config.LABEL_BACKREST + "=true" deletePropagation := metav1.DeletePropagationForeground err = apiserver.Clientset. @@ -179,7 +183,7 @@ func CreateBackup(request *msgs.CreateBackrestBackupRequest, ns, pgouser string) log.Error(err) } - //a hack sort of due to slow propagation + // a hack sort of due to slow propagation for i := 0; i < 3; i++ { jobList, err := apiserver.Clientset.BatchV1().Jobs(ns).List(ctx, metav1.ListOptions{LabelSelector: selector}) if err != nil { @@ -195,7 +199,7 @@ func CreateBackup(request *msgs.CreateBackrestBackupRequest, ns, pgouser string) // get pod name from cluster var podname string - podname, err = getBackrestRepoPodName(cluster, ns) + podname, err = getBackrestRepoPodName(cluster) if err != nil { log.Error(err) @@ -235,6 +239,54 @@ func CreateBackup(request *msgs.CreateBackrestBackupRequest, ns, pgouser string) return resp } +// DeleteBackup deletes a specific backup from a pgBackRest repository +func DeleteBackup(request msgs.DeleteBackrestBackupRequest) msgs.DeleteBackrestBackupResponse { + ctx := context.TODO() + response := msgs.DeleteBackrestBackupResponse{ + Status: msgs.Status{ + Code: msgs.Ok, + }, + } + + // first, make an attempt to get the cluster. if it does not exist, return + // an error + cluster, err := apiserver.Clientset.CrunchydataV1().Pgclusters(request.Namespace). + Get(ctx, request.ClusterName, metav1.GetOptions{}) + if err != nil { + log.Error(err) + response.Code = msgs.Error + response.Msg = err.Error() + return response + } + + // so, either we can delete the backup, or we cant, and we can only find out + // by trying. so here goes... + log.Debugf("attempting to delete backup %q cluster %q", request.Target, cluster.Name) + + // first, get the pgbackrest Pod name + podName, err := getBackrestRepoPodName(cluster) + if err != nil { + log.Error(err) + response.Code = msgs.Error + response.Msg = err.Error() + return response + } + + // set up the command + cmd := pgBackRestExpireCommand + cmd = append(cmd, request.Target) + + // and execute. if there is an error, return it, otherwise we are done + if _, stderr, err := kubeapi.ExecToPodThroughAPI(apiserver.RESTConfig, + apiserver.Clientset, cmd, containername, podName, cluster.Spec.Namespace, nil); err != nil { + log.Error(stderr) + response.Code = msgs.Error + response.Msg = stderr + } + + return response +} + func getBackupParams(identifier, clusterName, taskName, action, podName, containerName, imagePrefix, backupOpts, backrestStorageType, s3VerifyTLS, jobName, ns, pgouser string) *crv1.Pgtask { var newInstance *crv1.Pgtask spec := crv1.PgtaskSpec{} @@ -270,10 +322,10 @@ func getBackupParams(identifier, clusterName, taskName, action, podName, contain // getBackrestRepoPodName goes through the pod list to identify the // pgBackRest repo pod and then returns the pod name. -func getBackrestRepoPodName(cluster *crv1.Pgcluster, ns string) (string, error) { +func getBackrestRepoPodName(cluster *crv1.Pgcluster) (string, error) { ctx := context.TODO() - //look up the backrest-repo pod name + // look up the backrest-repo pod name selector := "pg-cluster=" + cluster.Spec.Name + ",pgo-backrest-repo=true" options := metav1.ListOptions{ @@ -281,7 +333,7 @@ func getBackrestRepoPodName(cluster *crv1.Pgcluster, ns string) (string, error) LabelSelector: selector, } - repopods, err := apiserver.Clientset.CoreV1().Pods(ns).List(ctx, options) + repopods, err := apiserver.Clientset.CoreV1().Pods(cluster.Namespace).List(ctx, options) if len(repopods.Items) != 1 { log.Errorf("pods len != 1 for cluster %s", cluster.Spec.Name) return "", errors.New("backrestrepo pod not found for cluster " + cluster.Spec.Name) @@ -301,7 +353,6 @@ func isPrimary(pod *v1.Pod, clusterName string) bool { return true } return false - } func isReady(pod *v1.Pod) bool { @@ -317,7 +368,6 @@ func isReady(pod *v1.Pod) bool { return false } return true - } // isPrimaryReady goes through the pod list to first identify the @@ -363,7 +413,7 @@ func ShowBackrest(name, selector, ns string) msgs.ShowBackrestResponse { } } - //get a list of all clusters + // get a list of all clusters clusterList, err := apiserver.Clientset. CrunchydataV1().Pgclusters(ns). List(ctx, metav1.ListOptions{LabelSelector: selector}) @@ -375,9 +425,10 @@ func ShowBackrest(name, selector, ns string) msgs.ShowBackrestResponse { log.Debugf("clusters found len is %d\n", len(clusterList.Items)) - for _, c := range clusterList.Items { - podname, err := getBackrestRepoPodName(&c, ns) + for i := range clusterList.Items { + c := &clusterList.Items[i] + podname, err := getBackrestRepoPodName(c) if err != nil { log.Error(err) response.Status.Code = msgs.Error @@ -409,11 +460,10 @@ func ShowBackrest(name, selector, ns string) msgs.ShowBackrestResponse { StorageType: storageType, } - verifyTLS, _ := strconv.ParseBool(operator.GetS3VerifyTLSSetting(&c)) + verifyTLS, _ := strconv.ParseBool(operator.GetS3VerifyTLSSetting(c)) // get the pgBackRest info using this legacy function info, err := getInfo(c.Name, storageType, podname, ns, verifyTLS) - // see if the function returned successfully, and if so, unmarshal the JSON if err != nil { log.Error(err) @@ -454,7 +504,6 @@ func getInfo(clusterName, storageType, podname, ns string, verifyTLS bool) (stri } output, stderr, err := kubeapi.ExecToPodThroughAPI(apiserver.RESTConfig, apiserver.Clientset, cmd, containername, podname, ns, nil) - if err != nil { log.Error(err, stderr) return "", err @@ -559,7 +608,7 @@ func Restore(request *msgs.RestoreRequest, ns, pgouser string) msgs.RestoreRespo return resp } - //create a pgtask for the restore workflow + // create a pgtask for the restore workflow if _, err := apiserver.Clientset.CrunchydataV1().Pgtasks(ns). Create(ctx, pgtask, metav1.CreateOptions{}); err != nil { resp.Status.Code = msgs.Error @@ -616,13 +665,13 @@ func createRestoreWorkflowTask(clusterName, ns string) (string, error) { taskName := clusterName + "-" + crv1.PgtaskWorkflowBackrestRestoreType - //delete any existing pgtask with the same name + // delete any existing pgtask with the same name if err := apiserver.Clientset.CrunchydataV1().Pgtasks(ns). Delete(ctx, taskName, metav1.DeleteOptions{}); err != nil && !kubeapi.IsNotFound(err) { return "", err } - //create pgtask CRD + // create pgtask CRD spec := crv1.PgtaskSpec{} spec.Namespace = ns spec.Name = clusterName + "-" + crv1.PgtaskWorkflowBackrestRestoreType diff --git a/internal/apiserver/backrestservice/backrestservice.go b/internal/apiserver/backrestservice/backrestservice.go index e436afb878..49a1edbbdb 100644 --- a/internal/apiserver/backrestservice/backrestservice.go +++ b/internal/apiserver/backrestservice/backrestservice.go @@ -17,12 +17,13 @@ limitations under the License. import ( "encoding/json" + "net/http" + "github.com/crunchydata/postgres-operator/internal/apiserver" "github.com/crunchydata/postgres-operator/internal/config" msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" - "net/http" ) // CreateBackupHandler ... @@ -76,6 +77,87 @@ func CreateBackupHandler(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(resp) } +// DeleteBackrestHandler deletes a targeted backup from a pgBackRest repository +// pgo delete backup hippo --target=pgbackrest-backup-id +func DeleteBackrestHandler(w http.ResponseWriter, r *http.Request) { + // swagger:operation DELETE /backrest backrestservice + /*``` + Delete a pgBackRest backup + */ + // --- + // produces: + // - application/json + // parameters: + // - name: "PostgreSQL Cluster Disk Utilization" + // in: "body" + // schema: + // "$ref": "#/definitions/DeleteBackrestBackupRequest" + // responses: + // '200': + // description: Output + // schema: + // "$ref": "#/definitions/DeleteBackrestBackupResponse" + log.Debug("backrestservice.DeleteBackrestHandler called") + + // first, check that the requesting user is authorized to make this request + username, err := apiserver.Authn(apiserver.DELETE_BACKUP_PERM, w, r) + if err != nil { + return + } + + // decode the request paramaeters + var request msgs.DeleteBackrestBackupRequest + + if err := json.NewDecoder(r.Body).Decode(&request); err != nil { + response := msgs.DeleteBackrestBackupResponse{ + Status: msgs.Status{ + Code: msgs.Error, + Msg: err.Error(), + }, + } + _ = json.NewEncoder(w).Encode(response) + return + } + + log.Debugf("DeleteBackrestHandler parameters [%+v]", request) + + // set some of the header...though we really should not be setting the HTTP + // Status upfront, but whatever + w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + // check that the client versions match. If they don't, error out + if request.ClientVersion != msgs.PGO_VERSION { + response := msgs.DeleteBackrestBackupResponse{ + Status: msgs.Status{ + Code: msgs.Error, + Msg: apiserver.VERSION_MISMATCH_ERROR, + }, + } + _ = json.NewEncoder(w).Encode(response) + return + } + + // ensure that the user has access to this namespace. if not, error out + if _, err := apiserver.GetNamespace(apiserver.Clientset, username, request.Namespace); err != nil { + response := msgs.DeleteBackrestBackupResponse{ + Status: msgs.Status{ + Code: msgs.Error, + Msg: err.Error(), + }, + } + _ = json.NewEncoder(w).Encode(response) + return + } + + // process the request + response := DeleteBackup(request) + + // turn the response into JSON + _ = json.NewEncoder(w).Encode(response) +} + // ShowBackrestHandler ... // returns a ShowBackrestResponse func ShowBackrestHandler(w http.ResponseWriter, r *http.Request) { @@ -150,7 +232,6 @@ func ShowBackrestHandler(w http.ResponseWriter, r *http.Request) { resp = ShowBackrest(backupname, selector, ns) json.NewEncoder(w).Encode(resp) - } // RestoreHandler ... diff --git a/internal/apiserver/routing/routes.go b/internal/apiserver/routing/routes.go index 96de93403a..378651e12b 100644 --- a/internal/apiserver/routing/routes.go +++ b/internal/apiserver/routing/routes.go @@ -75,6 +75,7 @@ func RegisterAllRoutes(r *mux.Router) { func RegisterBackrestSvcRoutes(r *mux.Router) { r.HandleFunc("/backrestbackup", backrestservice.CreateBackupHandler).Methods("POST") r.HandleFunc("/backrest/{name}", backrestservice.ShowBackrestHandler).Methods("GET") + r.HandleFunc("/backrest", backrestservice.DeleteBackrestHandler).Methods("DELETE") r.HandleFunc("/restore", backrestservice.RestoreHandler).Methods("POST") } diff --git a/pkg/apiservermsgs/backrestmsgs.go b/pkg/apiservermsgs/backrestmsgs.go index 12d72844b9..b1c4887fa9 100644 --- a/pkg/apiservermsgs/backrestmsgs.go +++ b/pkg/apiservermsgs/backrestmsgs.go @@ -32,6 +32,27 @@ type CreateBackrestBackupRequest struct { BackrestStorageType string } +// DeleteBackrestBackupRequest ... +// swagger:model +type DeleteBackrestBackupRequest struct { + // ClientVersion represents the version of the client that is making the API + // request + ClientVersion string + // ClusterName is the name of the pgcluster of which we want to delete the + // backup from + ClusterName string + // Namespace isthe namespace that the cluster is in + Namespace string + // Target is the nane of the backup to be deleted + Target string +} + +// DeleteBackrestBackupRequest ... +// swagger:model +type DeleteBackrestBackupResponse struct { + Status +} + // PgBackRestInfo and its associated structs are available for parsing the info // that comes from the output of the "pgbackrest info --output json" command type PgBackRestInfo struct { From efbe87706ec63510dd545e0261248796d922d246 Mon Sep 17 00:00:00 2001 From: andrewlecuyer <43458182+andrewlecuyer@users.noreply.github.com> Date: Mon, 21 Dec 2020 16:09:07 -0600 Subject: [PATCH 060/276] Update Init Flag if PGHA ConfigMap Already Exists If an existing PGHA ConfigMap is identified when bootstrapping a new PostgreSQL cluster, the Operator now updates the "init" flag within the existing ConfigMap (specifically by setting it to "true"). This ensures proper configuration of the "init" flag (and therefore the proper functionality of any logic within the Operator that relies on it, e.g. the execution of init logic within the 'crunchy-postgres-ha' container) when the PGHA ConfigMap is pre-defined prior to PostgreSQL cluster initialization. Issue: [ch9986] --- internal/controller/pod/inithandler.go | 6 ++++-- internal/operator/cluster/cluster.go | 21 +++++++++++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/internal/controller/pod/inithandler.go b/internal/controller/pod/inithandler.go index 64b3134e8c..c33be8d2ed 100644 --- a/internal/controller/pod/inithandler.go +++ b/internal/controller/pod/inithandler.go @@ -115,8 +115,10 @@ func (c *Controller) handleCommonInit(cluster *crv1.Pgcluster) error { cluster.ObjectMeta.Labels[config.LABEL_PGHA_SCOPE], cluster.Namespace) } - operator.UpdatePGHAConfigInitFlag(c.Client, false, cluster.Name, - cluster.Namespace) + if err := operator.UpdatePGHAConfigInitFlag(c.Client, false, cluster.Name, + cluster.Namespace); err != nil { + log.Error(err) + } return nil } diff --git a/internal/operator/cluster/cluster.go b/internal/operator/cluster/cluster.go index 74840a8113..09d91ce454 100644 --- a/internal/operator/cluster/cluster.go +++ b/internal/operator/cluster/cluster.go @@ -85,8 +85,13 @@ func AddClusterBase(clientset kubeapi.Interface, cl *crv1.Pgcluster, namespace s // logic following a restart of the container. // If the configmap already exists, the cluster creation will continue as this is required // for certain pgcluster upgrades. - if err = operator.CreatePGHAConfigMap(clientset, cl, namespace); err != nil && - !kerrors.IsAlreadyExists(err) { + if err = operator.CreatePGHAConfigMap(clientset, cl, + namespace); kerrors.IsAlreadyExists(err) { + log.Infof("found existing pgha ConfigMap for cluster %s, setting init flag to 'true'", + cl.GetName()) + err = operator.UpdatePGHAConfigInitFlag(clientset, true, cl.Name, cl.Namespace) + } + if err != nil { log.Error(err) publishClusterCreateFailure(cl, err.Error()) return @@ -234,8 +239,16 @@ func AddClusterBootstrap(clientset kubeapi.Interface, cluster *crv1.Pgcluster) e ctx := context.TODO() namespace := cluster.GetNamespace() - if err := operator.CreatePGHAConfigMap(clientset, cluster, namespace); err != nil && - !kerrors.IsAlreadyExists(err) { + var err error + + if err = operator.CreatePGHAConfigMap(clientset, cluster, + namespace); kerrors.IsAlreadyExists(err) { + log.Infof("found existing pgha ConfigMap for cluster %s, setting init flag to 'true'", + cluster.GetName()) + err = operator.UpdatePGHAConfigInitFlag(clientset, true, cluster.Name, cluster.Namespace) + } + if err != nil { + log.Error(err) publishClusterCreateFailure(cluster, err.Error()) return err } From ea1d3d1a3a2b81667dc07d68eaad75532d5029c7 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 21 Dec 2020 09:06:50 -0500 Subject: [PATCH 061/276] Apply linter recommendations to codebase This brings the codebase inline with the modern linting standards that we have defined, and thus allows us to run the full linting checks on any new changes. --- cmd/apiserver/main.go | 9 +- cmd/pgo-rmdata/main.go | 1 - cmd/pgo-rmdata/process.go | 61 ++-- cmd/pgo-scheduler/main.go | 35 +-- .../scheduler/configmapcontroller.go | 1 - .../scheduler/controllermanager.go | 13 - cmd/pgo-scheduler/scheduler/pgbackrest.go | 13 +- cmd/pgo-scheduler/scheduler/policy.go | 8 +- cmd/pgo-scheduler/scheduler/scheduler.go | 13 +- cmd/pgo/api/backrest.go | 4 +- cmd/pgo/api/cat.go | 4 +- cmd/pgo/api/cluster.go | 21 +- cmd/pgo/api/config.go | 9 +- cmd/pgo/api/df.go | 6 +- cmd/pgo/api/failover.go | 13 +- cmd/pgo/api/label.go | 5 +- cmd/pgo/api/namespace.go | 24 +- cmd/pgo/api/pgadmin.go | 11 +- cmd/pgo/api/pgbouncer.go | 22 +- cmd/pgo/api/pgdump.go | 10 +- cmd/pgo/api/pgorole.go | 23 +- cmd/pgo/api/pgouser.go | 23 +- cmd/pgo/api/policy.go | 23 +- cmd/pgo/api/pvc.go | 9 +- cmd/pgo/api/reload.go | 4 +- cmd/pgo/api/restart.go | 9 +- cmd/pgo/api/restore.go | 4 +- cmd/pgo/api/restoreDump.go | 4 +- cmd/pgo/api/scale.go | 6 +- cmd/pgo/api/scaledown.go | 11 +- cmd/pgo/api/schedule.go | 10 +- cmd/pgo/api/status.go | 9 +- cmd/pgo/api/test.go | 9 +- cmd/pgo/api/upgrade.go | 5 +- cmd/pgo/api/user.go | 23 +- cmd/pgo/api/version.go | 9 +- cmd/pgo/api/workflow.go | 9 +- cmd/pgo/cmd/auth.go | 15 +- cmd/pgo/cmd/backrest.go | 5 +- cmd/pgo/cmd/cat.go | 3 - cmd/pgo/cmd/cluster.go | 13 +- cmd/pgo/cmd/config.go | 3 - cmd/pgo/cmd/create.go | 96 +++--- cmd/pgo/cmd/delete.go | 6 +- cmd/pgo/cmd/df.go | 2 - cmd/pgo/cmd/failover.go | 4 - cmd/pgo/cmd/flags.go | 64 ++-- cmd/pgo/cmd/label.go | 11 +- cmd/pgo/cmd/namespace.go | 8 +- cmd/pgo/cmd/pgadmin.go | 2 - cmd/pgo/cmd/pgbouncer.go | 6 - cmd/pgo/cmd/pgdump.go | 6 +- cmd/pgo/cmd/pgorole.go | 11 +- cmd/pgo/cmd/pgouser.go | 11 +- cmd/pgo/cmd/policy.go | 12 +- cmd/pgo/cmd/pvc.go | 7 +- cmd/pgo/cmd/reload.go | 6 +- cmd/pgo/cmd/restart.go | 4 - cmd/pgo/cmd/restore.go | 8 +- cmd/pgo/cmd/root.go | 6 +- cmd/pgo/cmd/scale.go | 2 - cmd/pgo/cmd/scaledown.go | 6 +- cmd/pgo/cmd/schedule.go | 5 - cmd/pgo/cmd/show.go | 9 +- cmd/pgo/cmd/status.go | 5 - cmd/pgo/cmd/test.go | 10 +- cmd/pgo/cmd/update.go | 8 +- cmd/pgo/cmd/user.go | 5 - cmd/pgo/cmd/version.go | 2 - cmd/pgo/cmd/watch.go | 15 +- cmd/pgo/cmd/workflow.go | 4 - cmd/pgo/generatedocs.go | 1 - cmd/pgo/main.go | 1 - cmd/pgo/util/validation.go | 3 +- cmd/postgres-operator/main.go | 5 +- cmd/postgres-operator/open_telemetry.go | 2 +- .../apiserver/backrestservice/backrestimpl.go | 26 +- .../backrestservice/backrestservice.go | 14 +- .../backupoptions/backupoptionsutil.go | 8 +- .../backupoptions/pgbackrestoptions.go | 3 - .../apiserver/backupoptions/pgdumpoptions.go | 6 - internal/apiserver/catservice/catimpl.go | 5 +- internal/apiserver/catservice/catservice.go | 9 +- .../apiserver/clusterservice/clusterimpl.go | 78 +++-- .../clusterservice/clusterservice.go | 34 +-- .../apiserver/clusterservice/scaleimpl.go | 9 +- .../apiserver/clusterservice/scaleservice.go | 20 +- internal/apiserver/common.go | 3 +- .../apiserver/configservice/configservice.go | 9 +- internal/apiserver/dfservice/dfimpl.go | 5 +- internal/apiserver/dfservice/dfservice.go | 8 +- .../apiserver/failoverservice/failoverimpl.go | 6 +- .../failoverservice/failoverservice.go | 15 +- internal/apiserver/labelservice/labelimpl.go | 24 +- .../apiserver/labelservice/labelservice.go | 15 +- .../namespaceservice/namespaceimpl.go | 12 +- .../namespaceservice/namespaceservice.go | 18 +- internal/apiserver/perms.go | 7 +- .../apiserver/pgadminservice/pgadminimpl.go | 10 +- .../pgadminservice/pgadminservice.go | 22 +- .../pgbouncerservice/pgbouncerimpl.go | 20 +- .../pgbouncerservice/pgbouncerservice.go | 34 +-- .../apiserver/pgdumpservice/pgdumpimpl.go | 94 +----- .../apiserver/pgdumpservice/pgdumpservice.go | 18 +- .../apiserver/pgoroleservice/pgoroleimpl.go | 21 +- .../pgoroleservice/pgoroleservice.go | 19 +- .../apiserver/pgouserservice/pgouserimpl.go | 22 +- .../pgouserservice/pgouserservice.go | 19 +- .../apiserver/policyservice/policyimpl.go | 31 +- .../apiserver/policyservice/policyservice.go | 24 +- internal/apiserver/pvcservice/pvcservice.go | 9 +- .../apiserver/reloadservice/reloadimpl.go | 1 - .../apiserver/reloadservice/reloadservice.go | 9 +- .../restartservice/restartservice.go | 18 +- internal/apiserver/root.go | 50 ++- .../apiserver/scheduleservice/scheduleimpl.go | 12 +- .../scheduleservice/scheduleservice.go | 12 +- .../apiserver/statusservice/statusimpl.go | 20 +- .../apiserver/statusservice/statusservice.go | 9 +- .../apiserver/upgradeservice/upgradeimpl.go | 3 - .../upgradeservice/upgradeservice.go | 6 +- internal/apiserver/userservice/userimpl.go | 50 +-- .../apiserver/userservice/userimpl_test.go | 9 - internal/apiserver/userservice/userservice.go | 32 +- .../versionservice/versionservice.go | 9 +- .../apiserver/workflowservice/workflowimpl.go | 3 +- .../workflowservice/workflowservice.go | 9 +- internal/config/labels.go | 285 ++++++++++-------- internal/config/pgoconfig.go | 41 +-- internal/config/secrets.go | 1 + internal/config/volumes.go | 18 +- .../configmap/configmapcontroller.go | 4 - internal/controller/configmap/synchandler.go | 7 +- internal/controller/controllerutil.go | 5 +- internal/controller/job/backresthandler.go | 23 +- internal/controller/job/bootstraphandler.go | 2 +- internal/controller/job/jobcontroller.go | 12 +- internal/controller/job/jobevents.go | 8 +- internal/controller/job/pgdumphandler.go | 4 +- internal/controller/job/rmdatahandler.go | 5 +- .../controller/manager/controllermanager.go | 12 - internal/controller/manager/rbac.go | 3 - .../namespace/namespacecontroller.go | 4 - .../pgcluster/pgclustercontroller.go | 15 +- .../controller/pgpolicy/pgpolicycontroller.go | 11 +- .../pgreplica/pgreplicacontroller.go | 20 +- .../controller/pgtask/pgtaskcontroller.go | 33 +- internal/controller/pod/inithandler.go | 32 +- internal/controller/pod/podcontroller.go | 11 +- internal/controller/pod/podevents.go | 3 +- internal/controller/pod/promotionhandler.go | 8 +- internal/kubeapi/client_config.go | 6 +- internal/kubeapi/fake/fakeclients.go | 2 - internal/kubeapi/volumes_test.go | 4 +- internal/logging/loglib.go | 8 +- internal/ns/nslogic.go | 42 +-- internal/operator/backrest/backup.go | 10 +- internal/operator/backrest/repo.go | 9 +- internal/operator/backrest/restore.go | 10 +- internal/operator/backrest/stanza.go | 5 +- internal/operator/cluster/cluster.go | 41 ++- internal/operator/cluster/clusterlogic.go | 22 +- internal/operator/cluster/common.go | 1 + internal/operator/cluster/failover.go | 26 +- internal/operator/cluster/failoverlogic.go | 30 +- internal/operator/cluster/pgadmin.go | 5 +- internal/operator/cluster/pgbouncer.go | 2 +- internal/operator/cluster/rmdata.go | 2 +- internal/operator/cluster/service.go | 5 +- internal/operator/cluster/standby.go | 7 +- internal/operator/cluster/upgrade.go | 20 +- internal/operator/clusterutilities.go | 37 +-- internal/operator/clusterutilities_test.go | 4 - internal/operator/common.go | 23 +- internal/operator/config/configutil.go | 6 +- internal/operator/config/dcs.go | 7 - internal/operator/config/localdb.go | 16 +- .../operator/operatorupgrade/version-check.go | 12 +- internal/operator/pgbackrest.go | 4 +- internal/operator/pgdump/dump.go | 7 +- internal/operator/pgdump/restore.go | 5 +- internal/operator/pvc/pvc.go | 6 +- internal/operator/storage.go | 2 +- internal/operator/storage_test.go | 12 +- internal/operator/task/applypolicies.go | 9 +- internal/operator/task/rmbackups.go | 42 --- internal/operator/task/rmdata.go | 12 +- internal/operator/task/workflow.go | 7 +- internal/patroni/patroni.go | 18 +- internal/pgadmin/backoff.go | 1 + internal/pgadmin/backoff_test.go | 1 - internal/pgadmin/crypto_test.go | 6 +- internal/pgadmin/hash.go | 2 +- internal/pgadmin/runner.go | 12 +- internal/postgres/password/md5.go | 8 +- internal/postgres/password/md5_test.go | 1 - internal/postgres/password/password.go | 6 +- internal/postgres/password/password_test.go | 5 +- internal/postgres/password/scram.go | 1 - internal/postgres/password/scram_test.go | 8 +- internal/tlsutil/primitives_test.go | 12 +- internal/util/backrest.go | 4 +- internal/util/cluster.go | 49 ++- internal/util/failover.go | 33 +- internal/util/pgbouncer.go | 1 + internal/util/policy.go | 7 +- internal/util/secrets.go | 5 - internal/util/ssh.go | 1 + internal/util/util.go | 17 +- internal/util/util_test.go | 1 - pkg/apis/crunchydata.com/v1/common.go | 1 - pkg/apis/crunchydata.com/v1/register.go | 2 +- pkg/apis/crunchydata.com/v1/task.go | 1 - pkg/apiservermsgs/clustermsgs.go | 16 +- pkg/apiservermsgs/usermsgs.go | 8 +- pkg/apiservermsgs/usermsgs_test.go | 4 +- pkg/events/eventing.go | 13 +- pkg/events/eventtype.go | 2 + 218 files changed, 1321 insertions(+), 1754 deletions(-) delete mode 100644 internal/operator/task/rmbackups.go diff --git a/cmd/apiserver/main.go b/cmd/apiserver/main.go index 2c3858bb63..8b6b1216af 100644 --- a/cmd/apiserver/main.go +++ b/cmd/apiserver/main.go @@ -34,8 +34,10 @@ import ( ) // Created as part of the apiserver.WriteTLSCert call -const serverCertPath = "/tmp/server.crt" -const serverKeyPath = "/tmp/server.key" +const ( + serverCertPath = "/tmp/server.crt" + serverKeyPath = "/tmp/server.key" +) func main() { // Environment-overridden variables @@ -147,8 +149,9 @@ func main() { svrCertFile.Close() } + // #nosec: G402 cfg := &tls.Config{ - //specify pgo-apiserver in the CN....then, add ServerName: "pgo-apiserver", + // specify pgo-apiserver in the CN....then, add ServerName: "pgo-apiserver", ServerName: "pgo-apiserver", ClientAuth: tls.VerifyClientCertIfGiven, InsecureSkipVerify: tlsNoVerify, diff --git a/cmd/pgo-rmdata/main.go b/cmd/pgo-rmdata/main.go index 201b138130..b4c5c2c4fc 100644 --- a/cmd/pgo-rmdata/main.go +++ b/cmd/pgo-rmdata/main.go @@ -65,5 +65,4 @@ func main() { log.Infof("request is %s", request.String()) Delete(request) - } diff --git a/cmd/pgo-rmdata/process.go b/cmd/pgo-rmdata/process.go index d0d79744f6..b1eba3bc95 100644 --- a/cmd/pgo-rmdata/process.go +++ b/cmd/pgo-rmdata/process.go @@ -49,7 +49,7 @@ func Delete(request Request) { ctx := context.TODO() log.Infof("rmdata.Process %v", request) - //the case of 'pgo scaledown' + // the case of 'pgo scaledown' if request.IsReplica { log.Info("rmdata.Process scaledown replica use case") removeReplicaServices(request) @@ -57,7 +57,7 @@ func Delete(request Request) { if err != nil { log.Error(err) } - //delete the pgreplica CRD + // delete the pgreplica CRD if err := request.Clientset. CrunchydataV1().Pgreplicas(request.Namespace). Delete(ctx, request.ReplicaName, metav1.DeleteOptions{}); err != nil { @@ -83,13 +83,13 @@ func Delete(request Request) { removePVCs(pvcList, request) } - //scale down is its own use case so we leave when done + // scale down is its own use case so we leave when done return } if request.IsBackup { log.Info("rmdata.Process backup use case") - //the case of removing a backup using `pgo delete backup`, only applies to + // the case of removing a backup using `pgo delete backup`, only applies to // "backup-type=pgdump" removeBackupJobs(request) removeLogicalBackupPVCs(request) @@ -104,13 +104,13 @@ func Delete(request Request) { // executing asynchronously against any stale data removeSchedules(request) - //the user had done something like: - //pgo delete cluster mycluster --delete-data + // the user had done something like: + // pgo delete cluster mycluster --delete-data if request.RemoveData { removeUserSecrets(request) } - //handle the case of 'pgo delete cluster mycluster' + // handle the case of 'pgo delete cluster mycluster' removeCluster(request) if err := request.Clientset. CrunchydataV1().Pgclusters(request.Namespace). @@ -122,7 +122,7 @@ func Delete(request Request) { removePgreplicas(request) removePgtasks(request) removeClusterConfigmaps(request) - //removeClusterJobs(request) + // removeClusterJobs(request) if request.RemoveData { if pvcList, err := getInstancePVCs(request); err != nil { log.Error(err) @@ -171,7 +171,7 @@ func removeBackrestRepo(request Request) { log.Error(err) } - //delete the service for the backrest repo + // delete the service for the backrest repo err = request.Clientset. CoreV1().Services(request.Namespace). Delete(ctx, deploymentName, metav1.DeleteOptions{}) @@ -260,12 +260,11 @@ func removeCluster(request Request) { selector := fmt.Sprintf("%s=%s,%s!=true", config.LABEL_PG_CLUSTER, request.ClusterName, config.LABEL_PGO_BACKREST_REPO) + // if there is an error here, return as we cannot iterate over the deployment + // list deployments, err := request.Clientset. AppsV1().Deployments(request.Namespace). List(ctx, metav1.ListOptions{LabelSelector: selector}) - - // if there is an error here, return as we cannot iterate over the deployment - // list if err != nil { log.Error(err) return @@ -315,7 +314,7 @@ func removeReplica(request Request) error { return err } - //wait for the deployment to go away fully + // wait for the deployment to go away fully var completed bool for i := 0; i < maximumTries; i++ { _, err = request.Clientset. @@ -337,7 +336,7 @@ func removeReplica(request Request) error { func removeUserSecrets(request Request) { ctx := context.TODO() - //get all that match pg-cluster=db + // get all that match pg-cluster=db selector := config.LABEL_PG_CLUSTER + "=" + request.ClusterName secrets, err := request.Clientset. @@ -356,12 +355,11 @@ func removeUserSecrets(request Request) { } } } - } func removeAddons(request Request) { ctx := context.TODO() - //remove pgbouncer + // remove pgbouncer pgbouncerDepName := request.ClusterName + "-pgbouncer" @@ -370,7 +368,7 @@ func removeAddons(request Request) { AppsV1().Deployments(request.Namespace). Delete(ctx, pgbouncerDepName, metav1.DeleteOptions{PropagationPolicy: &deletePropagation}) - //delete the service name=-pgbouncer + // delete the service name=-pgbouncer _ = request.Clientset. CoreV1().Services(request.Namespace). @@ -380,7 +378,7 @@ func removeAddons(request Request) { func removeServices(request Request) { ctx := context.TODO() - //remove any service for this cluster + // remove any service for this cluster selector := config.LABEL_PG_CLUSTER + "=" + request.ClusterName @@ -400,13 +398,12 @@ func removeServices(request Request) { log.Error(err) } } - } func removePgreplicas(request Request) { ctx := context.TODO() - //get a list of pgreplicas for this cluster + // get a list of pgreplicas for this cluster replicaList, err := request.Clientset.CrunchydataV1().Pgreplicas(request.Namespace).List(ctx, metav1.ListOptions{ LabelSelector: config.LABEL_PG_CLUSTER + "=" + request.ClusterName, }) @@ -424,13 +421,12 @@ func removePgreplicas(request Request) { log.Warn(err) } } - } func removePgtasks(request Request) { ctx := context.TODO() - //get a list of pgtasks for this cluster + // get a list of pgtasks for this cluster taskList, err := request.Clientset. CrunchydataV1().Pgtasks(request.Namespace). List(ctx, metav1.ListOptions{LabelSelector: config.LABEL_PG_CLUSTER + "=" + request.ClusterName}) @@ -446,7 +442,6 @@ func removePgtasks(request Request) { log.Warn(err) } } - } // getInstancePVCs gets all the PVCs that are associated with PostgreSQL @@ -461,11 +456,10 @@ func getInstancePVCs(request Request) ([]string, error) { log.Debugf("instance pvcs overall selector: [%s]", selector) // get all of the PVCs to analyze (see the step below) + // if there is an error, return here and log the error in the calling function pvcs, err := request.Clientset. CoreV1().PersistentVolumeClaims(request.Namespace). List(ctx, metav1.ListOptions{LabelSelector: selector}) - - // if there is an error, return here and log the error in the calling function if err != nil { return pvcList, err } @@ -496,14 +490,14 @@ func getInstancePVCs(request Request) ([]string, error) { return pvcList, nil } -//get the pvc for this replica deployment +// get the pvc for this replica deployment func getReplicaPVC(request Request) ([]string, error) { ctx := context.TODO() pvcList := make([]string, 0) - //at this point, the naming convention is useful - //and ClusterName is the replica deployment name - //when isReplica=true + // at this point, the naming convention is useful + // and ClusterName is the replica deployment name + // when isReplica=true pvcList = append(pvcList, request.ReplicaName) // see if there are any tablespaces or WAL volumes assigned to this replica, @@ -515,11 +509,10 @@ func getReplicaPVC(request Request) ([]string, error) { selector := fmt.Sprintf("%s=%s", config.LABEL_PG_CLUSTER, request.ClusterName) // get all of the PVCs that are specific to this replica and remove them + // if there is an error, return here and log the error in the calling function pvcs, err := request.Clientset. CoreV1().PersistentVolumeClaims(request.Namespace). List(ctx, metav1.ListOptions{LabelSelector: selector}) - - // if there is an error, return here and log the error in the calling function if err != nil { return pvcList, err } @@ -547,7 +540,7 @@ func getReplicaPVC(request Request) ([]string, error) { return pvcList, nil } -func removePVCs(pvcList []string, request Request) error { +func removePVCs(pvcList []string, request Request) { ctx := context.TODO() for _, p := range pvcList { @@ -560,9 +553,6 @@ func removePVCs(pvcList []string, request Request) error { log.Error(err) } } - - return nil - } // removeBackupJobs removes any job associated with a backup. These include: @@ -591,7 +581,6 @@ func removeBackupJobs(request Request) { jobs, err := request.Clientset. BatchV1().Jobs(request.Namespace). List(ctx, metav1.ListOptions{LabelSelector: selector}) - if err != nil { log.Error(err) continue diff --git a/cmd/pgo-scheduler/main.go b/cmd/pgo-scheduler/main.go index 4de3ac6cfb..63e0c17a00 100644 --- a/cmd/pgo-scheduler/main.go +++ b/cmd/pgo-scheduler/main.go @@ -19,7 +19,6 @@ import ( "fmt" "os" "os/signal" - "strconv" "syscall" "time" @@ -40,16 +39,15 @@ import ( const ( schedulerLabel = "crunchy-scheduler=true" pgoNamespaceEnv = "PGO_OPERATOR_NAMESPACE" - timeoutEnv = "TIMEOUT" namespaceWorkerCount = 1 ) -var nsRefreshInterval = 10 * time.Minute -var installationName string -var pgoNamespace string -var timeout time.Duration -var seconds int -var clientset kubeapi.Interface +var ( + nsRefreshInterval = 10 * time.Minute + installationName string + pgoNamespace string + clientset kubeapi.Interface +) // NamespaceOperatingMode defines the namespace operating mode for the cluster, // e.g. "dynamic", "readonly" or "disabled". See type NamespaceOperatingMode @@ -61,7 +59,7 @@ func init() { log.SetLevel(log.InfoLevel) debugFlag := os.Getenv("CRUNCHY_DEBUG") - //add logging configuration + // add logging configuration crunchylog.CrunchyLogger(crunchylog.SetParameters()) if debugFlag == "true" { log.SetLevel(log.DebugLevel) @@ -82,20 +80,6 @@ func init() { log.WithFields(log.Fields{}).Fatalf("Failed to get PGO_OPERATOR_NAMESPACE environment: %s", pgoNamespaceEnv) } - secondsEnv := os.Getenv(timeoutEnv) - seconds = 300 - if secondsEnv == "" { - log.WithFields(log.Fields{}).Info("No timeout set, defaulting to 300 seconds") - } else { - seconds, err = strconv.Atoi(secondsEnv) - if err != nil { - log.WithFields(log.Fields{}).Fatalf("Failed to convert timeout env to seconds: %s", err) - } - } - - log.WithFields(log.Fields{}).Infof("Setting timeout to: %d", seconds) - timeout = time.Second * time.Duration(seconds) - clientset, err = kubeapi.NewClient() if err != nil { log.WithFields(log.Fields{}).Fatalf("Failed to connect to kubernetes: %s", err) @@ -116,7 +100,7 @@ func init() { func main() { log.Info("Starting Crunchy Scheduler") - //give time for pgo-event to start up + // give time for pgo-event to start up time.Sleep(time.Duration(5) * time.Second) scheduler := sched.New(schedulerLabel, pgoNamespace, clientset) @@ -150,7 +134,7 @@ func main() { log.WithFields(log.Fields{}).Fatalf("Failed to create controller manager: %s", err) os.Exit(2) } - controllerManager.RunAll() + _ = controllerManager.RunAll() // if the namespace operating mode is not disabled, then create and start a namespace // controller @@ -211,7 +195,6 @@ func setNamespaceOperatingMode(clientset kubernetes.Interface) error { // createAndStartNamespaceController creates a namespace controller and then starts it func createAndStartNamespaceController(kubeClientset kubernetes.Interface, controllerManager controller.Manager, stopCh <-chan struct{}) error { - nsKubeInformerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(kubeClientset, nsRefreshInterval, kubeinformers.WithTweakListOptions(func(options *metav1.ListOptions) { diff --git a/cmd/pgo-scheduler/scheduler/configmapcontroller.go b/cmd/pgo-scheduler/scheduler/configmapcontroller.go index 41372f96b5..95d21d883c 100644 --- a/cmd/pgo-scheduler/scheduler/configmapcontroller.go +++ b/cmd/pgo-scheduler/scheduler/configmapcontroller.go @@ -62,7 +62,6 @@ func (c *Controller) onDelete(obj interface{}) { // AddConfigMapEventHandler adds the pgcluster event handler to the pgcluster informer func (c *Controller) AddConfigMapEventHandler() { - c.Informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: c.onAdd, DeleteFunc: c.onDelete, diff --git a/cmd/pgo-scheduler/scheduler/controllermanager.go b/cmd/pgo-scheduler/scheduler/controllermanager.go index 41c09ef8ef..055527fc64 100644 --- a/cmd/pgo-scheduler/scheduler/controllermanager.go +++ b/cmd/pgo-scheduler/scheduler/controllermanager.go @@ -57,7 +57,6 @@ type controllerGroup struct { // namespace included in the 'namespaces' parameter. func NewControllerManager(namespaces []string, scheduler *Scheduler, installationName string, namespaceOperatingMode ns.NamespaceOperatingMode) (*ControllerManager, error) { - controllerManager := ControllerManager{ controllers: make(map[string]*controllerGroup), installationName: installationName, @@ -87,7 +86,6 @@ func NewControllerManager(namespaces []string, scheduler *Scheduler, installatio // informers for this resource. Each controller group also receives its own clients, which can then // be utilized by the controller within the controller group. func (c *ControllerManager) AddGroup(namespace string) error { - c.mgrMutex.Lock() defer c.mgrMutex.Unlock() @@ -103,7 +101,6 @@ func (c *ControllerManager) AddGroup(namespace string) error { // AddAndRunGroup is a convenience function that adds a controller group for the // namespace specified, and then immediately runs the controllers in that group. func (c *ControllerManager) AddAndRunGroup(namespace string) error { - if c.controllers[namespace] != nil { // first try to clean if one is not already in progress if err := c.clean(namespace); err != nil { @@ -137,7 +134,6 @@ func (c *ControllerManager) AddAndRunGroup(namespace string) error { // RemoveAll removes all controller groups managed by the controller manager, first stopping all // controllers within each controller group managed by the controller manager. func (c *ControllerManager) RemoveAll() { - c.mgrMutex.Lock() defer c.mgrMutex.Unlock() @@ -151,7 +147,6 @@ func (c *ControllerManager) RemoveAll() { // RemoveGroup removes the controller group for the namespace specified, first stopping all // controllers within that group func (c *ControllerManager) RemoveGroup(namespace string) { - c.mgrMutex.Lock() defer c.mgrMutex.Unlock() @@ -160,7 +155,6 @@ func (c *ControllerManager) RemoveGroup(namespace string) { // RunAll runs all controllers across all controller groups managed by the controller manager. func (c *ControllerManager) RunAll() error { - c.mgrMutex.Lock() defer c.mgrMutex.Unlock() @@ -177,7 +171,6 @@ func (c *ControllerManager) RunAll() error { // RunGroup runs the controllers within the controller group for the namespace specified. func (c *ControllerManager) RunGroup(namespace string) error { - c.mgrMutex.Lock() defer c.mgrMutex.Unlock() @@ -198,7 +191,6 @@ func (c *ControllerManager) RunGroup(namespace string) error { // addControllerGroup adds a new controller group for the namespace specified func (c *ControllerManager) addControllerGroup(namespace string) error { - if _, ok := c.controllers[namespace]; ok { log.Debugf("Controller Manager: a controller for namespace %s already exists", namespace) return controller.ErrControllerGroupExists @@ -241,7 +233,6 @@ func (c *ControllerManager) addControllerGroup(namespace string) error { // clean removes and controller groups that no longer correspond to a valid namespace within // the Kubernetes cluster, e.g. in the event that a namespace has been deleted. func (c *ControllerManager) clean(namespace string) error { - if !c.sem.TryAcquire(1) { return fmt.Errorf("controller group clean already in progress, namespace %s will not "+ "clean", namespace) @@ -278,7 +269,6 @@ func (c *ControllerManager) clean(namespace string) error { // hasListerPrivs verifies the Operator has the privileges required to start the controllers // for the namespace specified. func (c *ControllerManager) hasListerPrivs(namespace string) bool { - controllerGroup := c.controllers[namespace] var err error @@ -301,7 +291,6 @@ func (c *ControllerManager) hasListerPrivs(namespace string) bool { // runControllerGroup is responsible running the controllers for the controller group corresponding // to the namespace provided func (c *ControllerManager) runControllerGroup(namespace string) error { - controllerGroup := c.controllers[namespace] hasListerPrivs := c.hasListerPrivs(namespace) @@ -335,7 +324,6 @@ func (c *ControllerManager) runControllerGroup(namespace string) error { // queues associated with the controllers inside of the controller group are first shutdown // prior to removing the controller group. func (c *ControllerManager) removeControllerGroup(namespace string) { - if _, ok := c.controllers[namespace]; !ok { log.Debugf("Controller Manager: no controller group to remove for ns %s", namespace) return @@ -351,7 +339,6 @@ func (c *ControllerManager) removeControllerGroup(namespace string) { // done by calling the ShutdownWorker function associated with the controller. If the controller // does not have a ShutdownWorker function then no action is taken. func (c *ControllerManager) stopControllerGroup(namespace string) { - if _, ok := c.controllers[namespace]; !ok { log.Debugf("Controller Manager: unable to stop controller group for namespace %s because "+ "a controller group for this namespace does not exist", namespace) diff --git a/cmd/pgo-scheduler/scheduler/pgbackrest.go b/cmd/pgo-scheduler/scheduler/pgbackrest.go index 710e1f12d2..ef0ba90a00 100644 --- a/cmd/pgo-scheduler/scheduler/pgbackrest.go +++ b/cmd/pgo-scheduler/scheduler/pgbackrest.go @@ -62,7 +62,8 @@ func (b BackRestBackupJob) Run() { "container": b.container, "backupType": b.backupType, "cluster": b.cluster, - "storageType": b.storageType}) + "storageType": b.storageType, + }) contextLogger.Info("Running pgBackRest backup") @@ -76,11 +77,11 @@ func (b BackRestBackupJob) Run() { taskName := fmt.Sprintf("%s-%s-sch-backup", b.cluster, b.backupType) - //if the cluster is found, check for an annotation indicating it has not been upgraded - //if the annotation does not exist, then it is a new cluster and proceed as usual - //if the annotation is set to "true", the cluster has already been upgraded and can proceed but - //if the annotation is set to "false", this cluster will need to be upgraded before proceeding - //log the issue, then return + // if the cluster is found, check for an annotation indicating it has not been upgraded + // if the annotation does not exist, then it is a new cluster and proceed as usual + // if the annotation is set to "true", the cluster has already been upgraded and can proceed but + // if the annotation is set to "false", this cluster will need to be upgraded before proceeding + // log the issue, then return if cluster.Annotations[config.ANNOTATION_IS_UPGRADED] == config.ANNOTATIONS_FALSE { contextLogger.WithFields(log.Fields{ "task": taskName, diff --git a/cmd/pgo-scheduler/scheduler/policy.go b/cmd/pgo-scheduler/scheduler/policy.go index e2be356d07..15a93cf27f 100644 --- a/cmd/pgo-scheduler/scheduler/policy.go +++ b/cmd/pgo-scheduler/scheduler/policy.go @@ -60,7 +60,8 @@ func (p PolicyJob) Run() { contextLogger := log.WithFields(log.Fields{ "namespace": p.namespace, "policy": p.policy, - "cluster": p.cluster}) + "cluster": p.cluster, + }) contextLogger.Info("Running Policy schedule") @@ -98,7 +99,7 @@ func (p PolicyJob) Run() { data := make(map[string]string) data[filename] = string(policy.Spec.SQL) - var labels = map[string]string{ + labels := map[string]string{ "pg-cluster": p.cluster, } labels["pg-cluster"] = p.cluster @@ -146,7 +147,8 @@ func (p PolicyJob) Run() { var doc bytes.Buffer if err := config.PolicyJobTemplate.Execute(&doc, policyJob); err != nil { contextLogger.WithFields(log.Fields{ - "error": err}).Error("Failed to render job template") + "error": err, + }).Error("Failed to render job template") return } diff --git a/cmd/pgo-scheduler/scheduler/scheduler.go b/cmd/pgo-scheduler/scheduler/scheduler.go index a0f9b3dc46..8d6d326936 100644 --- a/cmd/pgo-scheduler/scheduler/scheduler.go +++ b/cmd/pgo-scheduler/scheduler/scheduler.go @@ -32,8 +32,8 @@ import ( func New(label, namespace string, client kubeapi.Interface) *Scheduler { clientset = client cronClient := cv3.New() - cronClient.AddFunc("* * * * *", phony) - cronClient.AddFunc("* * * * *", heartbeat) + _, _ = cronClient.AddFunc("* * * * *", phony) + _, _ = cronClient.AddFunc("* * * * *", heartbeat) return &Scheduler{ namespace: namespace, @@ -56,17 +56,17 @@ func (s *Scheduler) AddSchedule(config *v1.ConfigMap) error { var schedule ScheduleTemplate for _, data := range config.Data { if err := json.Unmarshal([]byte(data), &schedule); err != nil { - return fmt.Errorf("Failed unmarhsaling configMap: %s", err) + return fmt.Errorf("Failed unmarhsaling configMap: %w", err) } } if err := validate(schedule); err != nil { - return fmt.Errorf("Failed to validate schedule: %s", err) + return fmt.Errorf("Failed to validate schedule: %w", err) } id, err := s.schedule(schedule) if err != nil { - return fmt.Errorf("Failed to schedule configmap: %s", err) + return fmt.Errorf("Failed to schedule configmap: %w", err) } log.WithFields(log.Fields{ @@ -117,7 +117,8 @@ func phony() { // heartbeat modifies a sentinel file used as part of the liveness test // for the scheduler func heartbeat() { - err := ioutil.WriteFile("/tmp/scheduler.hb", []byte(time.Now().String()), 0644) + // #nosec: G303 + err := ioutil.WriteFile("/tmp/scheduler.hb", []byte(time.Now().String()), 0o600) if err != nil { log.Errorln("error writing heartbeat file: ", err) } diff --git a/cmd/pgo/api/backrest.go b/cmd/pgo/api/backrest.go index e0e087efbf..ff157058fe 100644 --- a/cmd/pgo/api/backrest.go +++ b/cmd/pgo/api/backrest.go @@ -17,6 +17,7 @@ package api import ( "bytes" + "context" "encoding/json" "fmt" "net/http" @@ -34,11 +35,12 @@ func DeleteBackup(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCre log.Debugf("DeleteBackup called [%+v]", request) + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/backrest" action := "DELETE" - req, err := http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, action, url, bytes.NewBuffer(jsonValue)) if err != nil { return response, err } diff --git a/cmd/pgo/api/cat.go b/cmd/pgo/api/cat.go index 00d17c7fb6..1da601649e 100644 --- a/cmd/pgo/api/cat.go +++ b/cmd/pgo/api/cat.go @@ -18,13 +18,13 @@ package api import ( "bytes" "encoding/json" + "net/http" + msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" - "net/http" ) func Cat(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.CatRequest) (msgs.CatResponse, error) { - var response msgs.CatResponse jsonValue, _ := json.Marshal(request) diff --git a/cmd/pgo/api/cluster.go b/cmd/pgo/api/cluster.go index 74407c0dbc..c51425b09b 100644 --- a/cmd/pgo/api/cluster.go +++ b/cmd/pgo/api/cluster.go @@ -17,6 +17,7 @@ package api import ( "bytes" + "context" "encoding/json" "fmt" "net/http" @@ -33,15 +34,15 @@ const ( ) func ShowCluster(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.ShowClusterRequest) (msgs.ShowClusterResponse, error) { - var response msgs.ShowClusterResponse + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := fmt.Sprintf(showClusterURL, SessionCredentials.APIServerURL) log.Debugf("showCluster called...[%s]", url) action := "POST" - req, err := http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, action, url, bytes.NewBuffer(jsonValue)) if err != nil { return response, err } @@ -68,20 +69,19 @@ func ShowCluster(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCred } return response, err - } func DeleteCluster(httpclient *http.Client, request *msgs.DeleteClusterRequest, SessionCredentials *msgs.BasicAuthCredentials) (msgs.DeleteClusterResponse, error) { - var response msgs.DeleteClusterResponse + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := fmt.Sprintf(deleteClusterURL, SessionCredentials.APIServerURL) log.Debugf("delete cluster called %s", url) action := "POST" - req, err := http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, action, url, bytes.NewBuffer(jsonValue)) if err != nil { response.Status.Code = msgs.Error return response, err @@ -110,19 +110,18 @@ func DeleteCluster(httpclient *http.Client, request *msgs.DeleteClusterRequest, } return response, err - } func CreateCluster(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.CreateClusterRequest) (msgs.CreateClusterResponse, error) { - var response msgs.CreateClusterResponse + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := fmt.Sprintf(createClusterURL, SessionCredentials.APIServerURL) log.Debugf("createCluster called...[%s]", url) action := "POST" - req, err := http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, action, url, bytes.NewBuffer(jsonValue)) if err != nil { return response, err } @@ -152,15 +151,14 @@ func CreateCluster(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCr } func UpdateCluster(httpclient *http.Client, request *msgs.UpdateClusterRequest, SessionCredentials *msgs.BasicAuthCredentials) (msgs.UpdateClusterResponse, error) { - //func UpdateCluster(httpclient *http.Client, arg, selector string, SessionCredentials *msgs.BasicAuthCredentials, autofailFlag, ns string) (msgs.UpdateClusterResponse, error) { - var response msgs.UpdateClusterResponse jsonValue, _ := json.Marshal(request) + ctx := context.TODO() url := fmt.Sprintf(updateClusterURL, SessionCredentials.APIServerURL) log.Debugf("update cluster called %s", url) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonValue)) if err != nil { response.Status.Code = msgs.Error return response, err @@ -189,5 +187,4 @@ func UpdateCluster(httpclient *http.Client, request *msgs.UpdateClusterRequest, } return response, err - } diff --git a/cmd/pgo/api/config.go b/cmd/pgo/api/config.go index 90848edcfd..c64be16cd1 100644 --- a/cmd/pgo/api/config.go +++ b/cmd/pgo/api/config.go @@ -16,21 +16,23 @@ package api */ import ( + "context" "encoding/json" "fmt" + "net/http" + msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" - "net/http" ) func ShowConfig(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, ns string) (msgs.ShowConfigResponse, error) { - var response msgs.ShowConfigResponse + ctx := context.TODO() url := SessionCredentials.APIServerURL + "/config?version=" + msgs.PGO_VERSION + "&namespace=" + ns log.Debug(url) - req, err := http.NewRequest("GET", url, nil) + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return response, err } @@ -59,5 +61,4 @@ func ShowConfig(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCrede } return response, err - } diff --git a/cmd/pgo/api/df.go b/cmd/pgo/api/df.go index fa993051aa..cd64ea10da 100644 --- a/cmd/pgo/api/df.go +++ b/cmd/pgo/api/df.go @@ -17,6 +17,7 @@ package api import ( "bytes" + "context" "encoding/json" "fmt" "net/http" @@ -33,12 +34,12 @@ func ShowDf(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentia log.Debugf("ShowDf called [%+v]", request) + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/df" action := "POST" - req, err := http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) - + req, err := http.NewRequestWithContext(ctx, action, url, bytes.NewBuffer(jsonValue)) if err != nil { return response, err } @@ -47,7 +48,6 @@ func ShowDf(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentia req.SetBasicAuth(SessionCredentials.Username, SessionCredentials.Password) resp, err := httpclient.Do(req) - if err != nil { return response, err } diff --git a/cmd/pgo/api/failover.go b/cmd/pgo/api/failover.go index 4ebbab9471..61731da12e 100644 --- a/cmd/pgo/api/failover.go +++ b/cmd/pgo/api/failover.go @@ -17,24 +17,26 @@ package api import ( "bytes" + "context" "encoding/json" "fmt" + "net/http" + msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" - "net/http" ) func CreateFailover(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.CreateFailoverRequest) (msgs.CreateFailoverResponse, error) { - var response msgs.CreateFailoverResponse + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/failover" log.Debugf("create failover called [%s]", url) action := "POST" - req, err := http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, action, url, bytes.NewBuffer(jsonValue)) if err != nil { return response, err } @@ -63,15 +65,15 @@ func CreateFailover(httpclient *http.Client, SessionCredentials *msgs.BasicAuthC } func QueryFailover(httpclient *http.Client, arg string, SessionCredentials *msgs.BasicAuthCredentials, ns string) (msgs.QueryFailoverResponse, error) { - var response msgs.QueryFailoverResponse + ctx := context.TODO() url := SessionCredentials.APIServerURL + "/failover/" + arg + "?version=" + msgs.PGO_VERSION + "&namespace=" + ns log.Debugf("query failover called [%s]", url) action := "GET" - req, err := http.NewRequest(action, url, nil) + req, err := http.NewRequestWithContext(ctx, action, url, nil) if err != nil { return response, err } @@ -97,5 +99,4 @@ func QueryFailover(httpclient *http.Client, arg string, SessionCredentials *msgs } return response, err - } diff --git a/cmd/pgo/api/label.go b/cmd/pgo/api/label.go index e083f998a8..b96facc729 100644 --- a/cmd/pgo/api/label.go +++ b/cmd/pgo/api/label.go @@ -18,13 +18,13 @@ package api import ( "bytes" "encoding/json" + "net/http" + msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" - "net/http" ) func LabelClusters(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.LabelRequest) (msgs.LabelResponse, error) { - var response msgs.LabelResponse url := SessionCredentials.APIServerURL + "/label" log.Debugf("label called...[%s]", url) @@ -61,7 +61,6 @@ func LabelClusters(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCr } func DeleteLabel(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.DeleteLabelRequest) (msgs.LabelResponse, error) { - var response msgs.LabelResponse url := SessionCredentials.APIServerURL + "/labeldelete" log.Debugf("delete label called...[%s]", url) diff --git a/cmd/pgo/api/namespace.go b/cmd/pgo/api/namespace.go index 96f10ba8d7..1648e02384 100644 --- a/cmd/pgo/api/namespace.go +++ b/cmd/pgo/api/namespace.go @@ -17,23 +17,25 @@ package api import ( "bytes" + "context" "encoding/json" "fmt" + "net/http" + msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" - "net/http" ) func ShowNamespace(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.ShowNamespaceRequest) (msgs.ShowNamespaceResponse, error) { - var resp msgs.ShowNamespaceResponse resp.Status.Code = msgs.Ok + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/namespace" log.Debugf("ShowNamespace called...[%s]", url) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonValue)) if err != nil { resp.Status.Code = msgs.Error return resp, err @@ -61,19 +63,18 @@ func ShowNamespace(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCr } return resp, err - } func CreateNamespace(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.CreateNamespaceRequest) (msgs.CreateNamespaceResponse, error) { - var resp msgs.CreateNamespaceResponse resp.Status.Code = msgs.Ok + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/namespacecreate" log.Debugf("CreateNamespace called...[%s]", url) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonValue)) if err != nil { resp.Status.Code = msgs.Error return resp, err @@ -107,15 +108,15 @@ func CreateNamespace(httpclient *http.Client, SessionCredentials *msgs.BasicAuth } func DeleteNamespace(httpclient *http.Client, request *msgs.DeleteNamespaceRequest, SessionCredentials *msgs.BasicAuthCredentials) (msgs.DeleteNamespaceResponse, error) { - var response msgs.DeleteNamespaceResponse url := SessionCredentials.APIServerURL + "/namespacedelete" log.Debugf("DeleteNamespace called [%s]", url) + ctx := context.TODO() jsonValue, _ := json.Marshal(request) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonValue)) if err != nil { response.Status.Code = msgs.Error return response, err @@ -142,18 +143,18 @@ func DeleteNamespace(httpclient *http.Client, request *msgs.DeleteNamespaceReque } return response, err - } -func UpdateNamespace(httpclient *http.Client, request *msgs.UpdateNamespaceRequest, SessionCredentials *msgs.BasicAuthCredentials) (msgs.UpdateNamespaceResponse, error) { +func UpdateNamespace(httpclient *http.Client, request *msgs.UpdateNamespaceRequest, SessionCredentials *msgs.BasicAuthCredentials) (msgs.UpdateNamespaceResponse, error) { var response msgs.UpdateNamespaceResponse url := SessionCredentials.APIServerURL + "/namespaceupdate" log.Debugf("UpdateNamespace called [%s]", url) + ctx := context.TODO() jsonValue, _ := json.Marshal(request) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonValue)) if err != nil { response.Status.Code = msgs.Error return response, err @@ -180,5 +181,4 @@ func UpdateNamespace(httpclient *http.Client, request *msgs.UpdateNamespaceReque } return response, err - } diff --git a/cmd/pgo/api/pgadmin.go b/cmd/pgo/api/pgadmin.go index 0d410355cd..7f4ac09d89 100644 --- a/cmd/pgo/api/pgadmin.go +++ b/cmd/pgo/api/pgadmin.go @@ -17,6 +17,7 @@ package api import ( "bytes" + "context" "encoding/json" "io/ioutil" "net/http" @@ -29,12 +30,13 @@ import ( func CreatePgAdmin(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.CreatePgAdminRequest) (msgs.CreatePgAdminResponse, error) { var response msgs.CreatePgAdminResponse + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/pgadmin" log.Debugf("createPgAdmin called...[%s]", url) action := "POST" - req, err := http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, action, url, bytes.NewBuffer(jsonValue)) if err != nil { return response, err } @@ -68,12 +70,13 @@ func CreatePgAdmin(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCr func DeletePgAdmin(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.DeletePgAdminRequest) (msgs.DeletePgAdminResponse, error) { var response msgs.DeletePgAdminResponse + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/pgadmin" log.Debugf("deletePgAdmin called...[%s]", url) action := "DELETE" - req, err := http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, action, url, bytes.NewBuffer(jsonValue)) if err != nil { return response, err } @@ -117,13 +120,13 @@ func ShowPgAdmin(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCred log.Debugf("ShowPgAdmin called [%+v]", request) // put the request into JSON format and format the URL and HTTP verb + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/pgadmin/show" action := "POST" // prepare the request! - httpRequest, err := http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) - + httpRequest, err := http.NewRequestWithContext(ctx, action, url, bytes.NewBuffer(jsonValue)) // if there is an error preparing the request, return here if err != nil { return msgs.ShowPgAdminResponse{}, err diff --git a/cmd/pgo/api/pgbouncer.go b/cmd/pgo/api/pgbouncer.go index efee86ca53..56be678166 100644 --- a/cmd/pgo/api/pgbouncer.go +++ b/cmd/pgo/api/pgbouncer.go @@ -17,22 +17,24 @@ package api import ( "bytes" + "context" "encoding/json" + "net/http" + msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" - "net/http" ) func CreatePgbouncer(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.CreatePgbouncerRequest) (msgs.CreatePgbouncerResponse, error) { - var response msgs.CreatePgbouncerResponse + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/pgbouncer" log.Debugf("createPgbouncer called...[%s]", url) action := "POST" - req, err := http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, action, url, bytes.NewBuffer(jsonValue)) if err != nil { return response, err } @@ -61,15 +63,15 @@ func CreatePgbouncer(httpclient *http.Client, SessionCredentials *msgs.BasicAuth } func DeletePgbouncer(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.DeletePgbouncerRequest) (msgs.DeletePgbouncerResponse, error) { - var response msgs.DeletePgbouncerResponse + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/pgbouncer" log.Debugf("deletePgbouncer called...[%s]", url) action := "DELETE" - req, err := http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, action, url, bytes.NewBuffer(jsonValue)) if err != nil { return response, err } @@ -108,13 +110,13 @@ func ShowPgBouncer(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCr log.Debugf("ShowPgBouncer called [%+v]", request) // put the request into JSON format and format the URL and HTTP verb + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/pgbouncer/show" action := "POST" // prepare the request! - httpRequest, err := http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) - + httpRequest, err := http.NewRequestWithContext(ctx, action, url, bytes.NewBuffer(jsonValue)) // if there is an error preparing the request, return here if err != nil { return msgs.ShowPgBouncerResponse{}, err @@ -127,7 +129,6 @@ func ShowPgBouncer(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCr // make the request! if there is an error making the request, return httpResponse, err := httpclient.Do(httpRequest) - if err != nil { return msgs.ShowPgBouncerResponse{}, err } @@ -162,13 +163,13 @@ func UpdatePgBouncer(httpclient *http.Client, SessionCredentials *msgs.BasicAuth log.Debugf("UpdatePgBouncer called [%+v]", request) // put the request into JSON format and format the URL and HTTP verb + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/pgbouncer" action := "PUT" // prepare the request! - httpRequest, err := http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) - + httpRequest, err := http.NewRequestWithContext(ctx, action, url, bytes.NewBuffer(jsonValue)) // if there is an error preparing the request, return here if err != nil { return msgs.UpdatePgBouncerResponse{}, err @@ -181,7 +182,6 @@ func UpdatePgBouncer(httpclient *http.Client, SessionCredentials *msgs.BasicAuth // make the request! if there is an error making the request, return httpResponse, err := httpclient.Do(httpRequest) - if err != nil { return msgs.UpdatePgBouncerResponse{}, err } diff --git a/cmd/pgo/api/pgdump.go b/cmd/pgo/api/pgdump.go index 3bc0804c7b..b228954c91 100644 --- a/cmd/pgo/api/pgdump.go +++ b/cmd/pgo/api/pgdump.go @@ -17,6 +17,7 @@ package api import ( "bytes" + "context" "encoding/json" "fmt" "net/http" @@ -26,14 +27,14 @@ import ( ) func ShowpgDump(httpclient *http.Client, arg, selector string, SessionCredentials *msgs.BasicAuthCredentials, ns string) (msgs.ShowBackupResponse, error) { - var response msgs.ShowBackupResponse url := SessionCredentials.APIServerURL + "/pgdump/" + arg + "?version=" + msgs.PGO_VERSION + "&selector=" + selector + "&namespace=" + ns log.Debugf("show pgdump called [%s]", url) + ctx := context.TODO() action := "GET" - req, err := http.NewRequest(action, url, nil) + req, err := http.NewRequestWithContext(ctx, action, url, nil) if err != nil { return response, err } @@ -58,13 +59,12 @@ func ShowpgDump(httpclient *http.Client, arg, selector string, SessionCredential } return response, err - } func CreatepgDumpBackup(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.CreatepgDumpBackupRequest) (msgs.CreatepgDumpBackupResponse, error) { - var response msgs.CreatepgDumpBackupResponse + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/pgdumpbackup" @@ -72,7 +72,7 @@ func CreatepgDumpBackup(httpclient *http.Client, SessionCredentials *msgs.BasicA log.Debugf("create pgdump backup called [%s]", url) action := "POST" - req, err := http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, action, url, bytes.NewBuffer(jsonValue)) if err != nil { return response, err } diff --git a/cmd/pgo/api/pgorole.go b/cmd/pgo/api/pgorole.go index 804f0c1eb2..1157677fba 100644 --- a/cmd/pgo/api/pgorole.go +++ b/cmd/pgo/api/pgorole.go @@ -17,22 +17,24 @@ package api import ( "bytes" + "context" "encoding/json" "fmt" + "net/http" + msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" - "net/http" ) func ShowPgorole(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.ShowPgoroleRequest) (msgs.ShowPgoroleResponse, error) { - var response msgs.ShowPgoroleResponse + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/pgoroleshow" log.Debugf("ShowPgorole called...[%s]", url) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonValue)) if err != nil { response.Status.Code = msgs.Error return response, err @@ -58,18 +60,18 @@ func ShowPgorole(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCred } return response, err - } -func CreatePgorole(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.CreatePgoroleRequest) (msgs.CreatePgoroleResponse, error) { +func CreatePgorole(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.CreatePgoroleRequest) (msgs.CreatePgoroleResponse, error) { var resp msgs.CreatePgoroleResponse resp.Status.Code = msgs.Ok + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/pgorolecreate" log.Debugf("CreatePgorole called...[%s]", url) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonValue)) if err != nil { resp.Status.Code = msgs.Error return resp, err @@ -103,15 +105,15 @@ func CreatePgorole(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCr } func DeletePgorole(httpclient *http.Client, request *msgs.DeletePgoroleRequest, SessionCredentials *msgs.BasicAuthCredentials) (msgs.DeletePgoroleResponse, error) { - var response msgs.DeletePgoroleResponse url := SessionCredentials.APIServerURL + "/pgoroledelete" log.Debugf("DeletePgorole called [%s]", url) + ctx := context.TODO() jsonValue, _ := json.Marshal(request) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonValue)) if err != nil { response.Status.Code = msgs.Error return response, err @@ -138,18 +140,17 @@ func DeletePgorole(httpclient *http.Client, request *msgs.DeletePgoroleRequest, } return response, err - } func UpdatePgorole(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.UpdatePgoroleRequest) (msgs.UpdatePgoroleResponse, error) { - var response msgs.UpdatePgoroleResponse + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/pgoroleupdate" log.Debugf("UpdatePgorole called...[%s]", url) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonValue)) if err != nil { return response, err } diff --git a/cmd/pgo/api/pgouser.go b/cmd/pgo/api/pgouser.go index e0026d20ca..9f8cfaf63b 100644 --- a/cmd/pgo/api/pgouser.go +++ b/cmd/pgo/api/pgouser.go @@ -17,22 +17,24 @@ package api import ( "bytes" + "context" "encoding/json" "fmt" + "net/http" + msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" - "net/http" ) func ShowPgouser(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.ShowPgouserRequest) (msgs.ShowPgouserResponse, error) { - var response msgs.ShowPgouserResponse + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/pgousershow" log.Debugf("ShowPgouser called...[%s]", url) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonValue)) if err != nil { response.Status.Code = msgs.Error return response, err @@ -58,18 +60,18 @@ func ShowPgouser(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCred } return response, err - } -func CreatePgouser(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.CreatePgouserRequest) (msgs.CreatePgouserResponse, error) { +func CreatePgouser(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.CreatePgouserRequest) (msgs.CreatePgouserResponse, error) { var resp msgs.CreatePgouserResponse resp.Status.Code = msgs.Ok + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/pgousercreate" log.Debugf("CreatePgouser called...[%s]", url) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonValue)) if err != nil { resp.Status.Code = msgs.Error return resp, err @@ -103,15 +105,15 @@ func CreatePgouser(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCr } func DeletePgouser(httpclient *http.Client, request *msgs.DeletePgouserRequest, SessionCredentials *msgs.BasicAuthCredentials) (msgs.DeletePgouserResponse, error) { - var response msgs.DeletePgouserResponse url := SessionCredentials.APIServerURL + "/pgouserdelete" log.Debugf("DeletePgouser called [%s]", url) + ctx := context.TODO() jsonValue, _ := json.Marshal(request) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonValue)) if err != nil { response.Status.Code = msgs.Error return response, err @@ -138,18 +140,17 @@ func DeletePgouser(httpclient *http.Client, request *msgs.DeletePgouserRequest, } return response, err - } func UpdatePgouser(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.UpdatePgouserRequest) (msgs.UpdatePgouserResponse, error) { - var response msgs.UpdatePgouserResponse + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/pgouserupdate" log.Debugf("UpdatePgouser called...[%s]", url) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonValue)) if err != nil { return response, err } diff --git a/cmd/pgo/api/policy.go b/cmd/pgo/api/policy.go index b7e9cf5d6f..61b4f4842c 100644 --- a/cmd/pgo/api/policy.go +++ b/cmd/pgo/api/policy.go @@ -17,23 +17,25 @@ package api import ( "bytes" + "context" "encoding/json" "fmt" + "net/http" + msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" - "net/http" ) func ShowPolicy(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.ShowPolicyRequest) (msgs.ShowPolicyResponse, error) { - var response msgs.ShowPolicyResponse + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/showpolicies" log.Debugf("showPolicy called...[%s]", url) action := "POST" - req, err := http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, action, url, bytes.NewBuffer(jsonValue)) if err != nil { response.Status.Code = msgs.Error return response, err @@ -59,19 +61,19 @@ func ShowPolicy(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCrede } return response, err - } -func CreatePolicy(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.CreatePolicyRequest) (msgs.CreatePolicyResponse, error) { +func CreatePolicy(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.CreatePolicyRequest) (msgs.CreatePolicyResponse, error) { var resp msgs.CreatePolicyResponse resp.Status.Code = msgs.Ok + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/policies" log.Debugf("createPolicy called...[%s]", url) action := "POST" - req, err := http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, action, url, bytes.NewBuffer(jsonValue)) if err != nil { resp.Status.Code = msgs.Error return resp, err @@ -105,16 +107,16 @@ func CreatePolicy(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCre } func DeletePolicy(httpclient *http.Client, request *msgs.DeletePolicyRequest, SessionCredentials *msgs.BasicAuthCredentials) (msgs.DeletePolicyResponse, error) { - var response msgs.DeletePolicyResponse url := SessionCredentials.APIServerURL + "/policiesdelete" log.Debugf("delete policy called [%s]", url) + ctx := context.TODO() action := "POST" jsonValue, _ := json.Marshal(request) - req, err := http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, action, url, bytes.NewBuffer(jsonValue)) if err != nil { response.Status.Code = msgs.Error return response, err @@ -141,19 +143,18 @@ func DeletePolicy(httpclient *http.Client, request *msgs.DeletePolicyRequest, Se } return response, err - } func ApplyPolicy(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.ApplyPolicyRequest) (msgs.ApplyPolicyResponse, error) { - var response msgs.ApplyPolicyResponse + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/policies/apply" log.Debugf("applyPolicy called...[%s]", url) action := "POST" - req, err := http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, action, url, bytes.NewBuffer(jsonValue)) if err != nil { return response, err } diff --git a/cmd/pgo/api/pvc.go b/cmd/pgo/api/pvc.go index f4fac4ceb4..4c51b05423 100644 --- a/cmd/pgo/api/pvc.go +++ b/cmd/pgo/api/pvc.go @@ -17,17 +17,19 @@ package api import ( "bytes" + "context" "encoding/json" "fmt" + "net/http" + msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" - "net/http" ) func ShowPVC(httpclient *http.Client, request *msgs.ShowPVCRequest, SessionCredentials *msgs.BasicAuthCredentials) (msgs.ShowPVCResponse, error) { - var response msgs.ShowPVCResponse + ctx := context.TODO() url := SessionCredentials.APIServerURL + "/showpvc" log.Debugf("ShowPVC called...[%s]", url) @@ -35,7 +37,7 @@ func ShowPVC(httpclient *http.Client, request *msgs.ShowPVCRequest, SessionCrede log.Debugf("ShowPVC called...[%s]", url) action := "POST" - req, err := http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, action, url, bytes.NewBuffer(jsonValue)) if err != nil { return response, err } @@ -61,5 +63,4 @@ func ShowPVC(httpclient *http.Client, request *msgs.ShowPVCRequest, SessionCrede } return response, err - } diff --git a/cmd/pgo/api/reload.go b/cmd/pgo/api/reload.go index 9235cc1ea9..ee33d79b76 100644 --- a/cmd/pgo/api/reload.go +++ b/cmd/pgo/api/reload.go @@ -18,13 +18,13 @@ package api import ( "bytes" "encoding/json" + "net/http" + msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" - "net/http" ) func Reload(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.ReloadRequest) (msgs.ReloadResponse, error) { - var response msgs.ReloadResponse jsonValue, _ := json.Marshal(request) diff --git a/cmd/pgo/api/restart.go b/cmd/pgo/api/restart.go index 13dc205972..f73f6cc249 100644 --- a/cmd/pgo/api/restart.go +++ b/cmd/pgo/api/restart.go @@ -17,6 +17,7 @@ package api import ( "bytes" + "context" "encoding/json" "fmt" "net/http" @@ -29,12 +30,12 @@ import ( // a PG cluster or one or more instances within it. func Restart(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.RestartRequest) (msgs.RestartResponse, error) { - var response msgs.RestartResponse + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := fmt.Sprintf("%s/%s", SessionCredentials.APIServerURL, "restart") - req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonValue)) if err != nil { return response, err } @@ -69,11 +70,11 @@ func Restart(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredenti // cluster specified. func QueryRestart(httpclient *http.Client, clusterName string, SessionCredentials *msgs.BasicAuthCredentials, namespace string) (msgs.QueryRestartResponse, error) { - var response msgs.QueryRestartResponse + ctx := context.TODO() url := fmt.Sprintf("%s/%s/%s", SessionCredentials.APIServerURL, "restart", clusterName) - req, err := http.NewRequest(http.MethodGet, url, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return response, err } diff --git a/cmd/pgo/api/restore.go b/cmd/pgo/api/restore.go index e22cea904b..f80efe32f5 100644 --- a/cmd/pgo/api/restore.go +++ b/cmd/pgo/api/restore.go @@ -18,13 +18,13 @@ package api import ( "bytes" "encoding/json" + "net/http" + msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" - "net/http" ) func Restore(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.RestoreRequest) (msgs.RestoreResponse, error) { - var response msgs.RestoreResponse jsonValue, _ := json.Marshal(request) diff --git a/cmd/pgo/api/restoreDump.go b/cmd/pgo/api/restoreDump.go index bd911c1b75..6e1f918f7d 100644 --- a/cmd/pgo/api/restoreDump.go +++ b/cmd/pgo/api/restoreDump.go @@ -18,13 +18,13 @@ package api import ( "bytes" "encoding/json" + "net/http" + msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" - "net/http" ) func RestoreDump(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.PgRestoreRequest) (msgs.RestoreResponse, error) { - var response msgs.RestoreResponse jsonValue, _ := json.Marshal(request) diff --git a/cmd/pgo/api/scale.go b/cmd/pgo/api/scale.go index 6defb09127..87eae783f3 100644 --- a/cmd/pgo/api/scale.go +++ b/cmd/pgo/api/scale.go @@ -16,6 +16,7 @@ package api */ import ( + "context" "encoding/json" "fmt" "net/http" @@ -28,14 +29,14 @@ import ( func ScaleCluster(httpclient *http.Client, arg string, ReplicaCount int, StorageConfig, NodeLabel, CCPImageTag, ServiceType string, SessionCredentials *msgs.BasicAuthCredentials, ns string) (msgs.ClusterScaleResponse, error) { - var response msgs.ClusterScaleResponse url := fmt.Sprintf("%s/clusters/scale/%s", SessionCredentials.APIServerURL, arg) log.Debug(url) + ctx := context.TODO() action := "GET" - req, err := http.NewRequest(action, url, nil) + req, err := http.NewRequestWithContext(ctx, action, url, nil) if err != nil { return response, err } @@ -71,5 +72,4 @@ func ScaleCluster(httpclient *http.Client, arg string, ReplicaCount int, } return response, err - } diff --git a/cmd/pgo/api/scaledown.go b/cmd/pgo/api/scaledown.go index 1cc6691b72..9075c23074 100644 --- a/cmd/pgo/api/scaledown.go +++ b/cmd/pgo/api/scaledown.go @@ -16,6 +16,7 @@ package api */ import ( + "context" "encoding/json" "fmt" "net/http" @@ -29,13 +30,13 @@ import ( func ScaleDownCluster(httpclient *http.Client, clusterName, ScaleDownTarget string, DeleteData bool, SessionCredentials *msgs.BasicAuthCredentials, ns string) (msgs.ScaleDownResponse, error) { - var response msgs.ScaleDownResponse url := fmt.Sprintf("%s/scaledown/%s", SessionCredentials.APIServerURL, clusterName) log.Debug(url) + ctx := context.TODO() action := "GET" - req, err := http.NewRequest(action, url, nil) + req, err := http.NewRequestWithContext(ctx, action, url, nil) if err != nil { return response, err } @@ -67,19 +68,18 @@ func ScaleDownCluster(httpclient *http.Client, clusterName, ScaleDownTarget stri } return response, err - } func ScaleQuery(httpclient *http.Client, arg string, SessionCredentials *msgs.BasicAuthCredentials, ns string) (msgs.ScaleQueryResponse, error) { - var response msgs.ScaleQueryResponse url := SessionCredentials.APIServerURL + "/scale/" + arg + "?version=" + msgs.PGO_VERSION + "&namespace=" + ns log.Debug(url) + ctx := context.TODO() action := "GET" - req, err := http.NewRequest(action, url, nil) + req, err := http.NewRequestWithContext(ctx, action, url, nil) if err != nil { return response, err } @@ -105,5 +105,4 @@ func ScaleQuery(httpclient *http.Client, arg string, SessionCredentials *msgs.Ba } return response, err - } diff --git a/cmd/pgo/api/schedule.go b/cmd/pgo/api/schedule.go index 4007e77e1b..444bd04832 100644 --- a/cmd/pgo/api/schedule.go +++ b/cmd/pgo/api/schedule.go @@ -17,6 +17,7 @@ package api import ( "bytes" + "context" "encoding/json" "fmt" "net/http" @@ -34,12 +35,13 @@ const ( func CreateSchedule(client *http.Client, SessionCredentials *msgs.BasicAuthCredentials, r *msgs.CreateScheduleRequest) (msgs.CreateScheduleResponse, error) { var response msgs.CreateScheduleResponse + ctx := context.TODO() jsonValue, _ := json.Marshal(r) url := fmt.Sprintf(createScheduleURL, SessionCredentials.APIServerURL) log.Debugf("create schedule called [%s]", url) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonValue)) if err != nil { return response, err } @@ -69,12 +71,13 @@ func CreateSchedule(client *http.Client, SessionCredentials *msgs.BasicAuthCrede func DeleteSchedule(client *http.Client, SessionCredentials *msgs.BasicAuthCredentials, r *msgs.DeleteScheduleRequest) (msgs.DeleteScheduleResponse, error) { var response msgs.DeleteScheduleResponse + ctx := context.TODO() jsonValue, _ := json.Marshal(r) url := fmt.Sprintf(deleteScheduleURL, SessionCredentials.APIServerURL) log.Debugf("delete schedule called [%s]", url) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonValue)) if err != nil { return response, err } @@ -105,11 +108,12 @@ func DeleteSchedule(client *http.Client, SessionCredentials *msgs.BasicAuthCrede func ShowSchedule(client *http.Client, SessionCredentials *msgs.BasicAuthCredentials, r *msgs.ShowScheduleRequest) (msgs.ShowScheduleResponse, error) { var response msgs.ShowScheduleResponse + ctx := context.TODO() jsonValue, _ := json.Marshal(r) url := fmt.Sprintf(showScheduleURL, SessionCredentials.APIServerURL) log.Debugf("show schedule called [%s]", url) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonValue)) if err != nil { return response, err } diff --git a/cmd/pgo/api/status.go b/cmd/pgo/api/status.go index ad70bd2f96..9def02a132 100644 --- a/cmd/pgo/api/status.go +++ b/cmd/pgo/api/status.go @@ -16,21 +16,23 @@ package api */ import ( + "context" "encoding/json" "fmt" + "net/http" + msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" - "net/http" ) func ShowStatus(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, ns string) (msgs.StatusResponse, error) { - var response msgs.StatusResponse url := SessionCredentials.APIServerURL + "/status?version=" + msgs.PGO_VERSION + "&namespace=" + ns log.Debug(url) + ctx := context.TODO() action := "GET" - req, err := http.NewRequest(action, url, nil) + req, err := http.NewRequestWithContext(ctx, action, url, nil) if err != nil { return response, err } @@ -55,5 +57,4 @@ func ShowStatus(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCrede } return response, err - } diff --git a/cmd/pgo/api/test.go b/cmd/pgo/api/test.go index 887d67b056..ca70b5c132 100644 --- a/cmd/pgo/api/test.go +++ b/cmd/pgo/api/test.go @@ -17,23 +17,25 @@ package api import ( "bytes" + "context" "encoding/json" "fmt" + "net/http" + msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" - "net/http" ) func ShowTest(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.ClusterTestRequest) (msgs.ClusterTestResponse, error) { - var response msgs.ClusterTestResponse + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/testclusters" log.Debug(url) action := "POST" - req, err := http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, action, url, bytes.NewBuffer(jsonValue)) if err != nil { return response, err } @@ -58,5 +60,4 @@ func ShowTest(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredent } return response, err - } diff --git a/cmd/pgo/api/upgrade.go b/cmd/pgo/api/upgrade.go index 6079a29023..3613b4527a 100644 --- a/cmd/pgo/api/upgrade.go +++ b/cmd/pgo/api/upgrade.go @@ -17,6 +17,7 @@ package api import ( "bytes" + "context" "encoding/json" "net/http" @@ -25,15 +26,15 @@ import ( ) func CreateUpgrade(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.CreateUpgradeRequest) (msgs.CreateUpgradeResponse, error) { - var response msgs.CreateUpgradeResponse + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/upgrades" log.Debugf("CreateUpgrade called...[%s]", url) action := "POST" - req, err := http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, action, url, bytes.NewBuffer(jsonValue)) if err != nil { return response, err } diff --git a/cmd/pgo/api/user.go b/cmd/pgo/api/user.go index 38424ab17b..ed1ad4fda5 100644 --- a/cmd/pgo/api/user.go +++ b/cmd/pgo/api/user.go @@ -17,25 +17,27 @@ package api import ( "bytes" + "context" "encoding/json" "fmt" + "net/http" + msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" - "net/http" ) func ShowUser(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.ShowUserRequest) (msgs.ShowUserResponse, error) { - var response msgs.ShowUserResponse response.Status.Code = msgs.Ok request.ClientVersion = msgs.PGO_VERSION + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/usershow" log.Debugf("ShowUser called...[%s]", url) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonValue)) if err != nil { response.Status.Code = msgs.Error return response, err @@ -62,20 +64,20 @@ func ShowUser(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredent } return response, err - } -func CreateUser(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.CreateUserRequest) (msgs.CreateUserResponse, error) { +func CreateUser(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.CreateUserRequest) (msgs.CreateUserResponse, error) { var response msgs.CreateUserResponse request.ClientVersion = msgs.PGO_VERSION + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/usercreate" log.Debugf("createUsers called...[%s]", url) action := "POST" - req, err := http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, action, url, bytes.NewBuffer(jsonValue)) if err != nil { return response, err } @@ -104,17 +106,17 @@ func CreateUser(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCrede } func DeleteUser(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.DeleteUserRequest) (msgs.DeleteUserResponse, error) { - var response msgs.DeleteUserResponse request.ClientVersion = msgs.PGO_VERSION + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/userdelete" log.Debugf("deleteUser called...[%s]", url) action := "POST" - req, err := http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, action, url, bytes.NewBuffer(jsonValue)) if err != nil { return response, err } @@ -140,20 +142,19 @@ func DeleteUser(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCrede } return response, err - } func UpdateUser(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, request *msgs.UpdateUserRequest) (msgs.UpdateUserResponse, error) { - var response msgs.UpdateUserResponse request.ClientVersion = msgs.PGO_VERSION + ctx := context.TODO() jsonValue, _ := json.Marshal(request) url := SessionCredentials.APIServerURL + "/userupdate" log.Debugf("UpdateUser called...[%s]", url) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonValue)) + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonValue)) if err != nil { return response, err } diff --git a/cmd/pgo/api/version.go b/cmd/pgo/api/version.go index 9ca743add1..a48ac5bd8e 100644 --- a/cmd/pgo/api/version.go +++ b/cmd/pgo/api/version.go @@ -16,23 +16,25 @@ package api */ import ( + "context" "encoding/json" "fmt" + "net/http" + msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" - "net/http" ) func ShowVersion(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials) (msgs.VersionResponse, error) { - var response msgs.VersionResponse log.Debug("ShowVersion called ") + ctx := context.TODO() url := SessionCredentials.APIServerURL + "/version" log.Debug(url) - req, err := http.NewRequest("GET", url, nil) + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return response, err } @@ -61,5 +63,4 @@ func ShowVersion(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCred } return response, err - } diff --git a/cmd/pgo/api/workflow.go b/cmd/pgo/api/workflow.go index 3289329aa1..46381889f4 100644 --- a/cmd/pgo/api/workflow.go +++ b/cmd/pgo/api/workflow.go @@ -16,22 +16,24 @@ package api */ import ( + "context" "encoding/json" "fmt" + "net/http" + msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" - "net/http" ) func ShowWorkflow(httpclient *http.Client, workflowID string, SessionCredentials *msgs.BasicAuthCredentials, ns string) (msgs.ShowWorkflowResponse, error) { - var response msgs.ShowWorkflowResponse url := SessionCredentials.APIServerURL + "/workflow/" + workflowID + "?version=" + msgs.PGO_VERSION + "&namespace=" + ns log.Debugf("ShowWorkflow called...[%s]", url) + ctx := context.TODO() action := "GET" - req, err := http.NewRequest(action, url, nil) + req, err := http.NewRequestWithContext(ctx, action, url, nil) if err != nil { return response, err } @@ -56,5 +58,4 @@ func ShowWorkflow(httpclient *http.Client, workflowID string, SessionCredentials } return response, err - } diff --git a/cmd/pgo/cmd/auth.go b/cmd/pgo/cmd/auth.go index 322e5f5e9c..fda8689df6 100644 --- a/cmd/pgo/cmd/auth.go +++ b/cmd/pgo/cmd/auth.go @@ -109,7 +109,7 @@ func getCredentialsFromFile() msgs.BasicAuthCredentials { fullPath := dir + "/" + ".pgouser" var creds msgs.BasicAuthCredentials - //look in env var for pgouser file + // look in env var for pgouser file pgoUser := os.Getenv(pgoUserFileEnvVar) if pgoUser != "" { fullPath = pgoUser @@ -125,7 +125,7 @@ func getCredentialsFromFile() msgs.BasicAuthCredentials { found = true } - //look in home directory for .pgouser file + // look in home directory for .pgouser file if !found { log.Debugf("looking in %s for credentials", fullPath) dat, err := ioutil.ReadFile(fullPath) @@ -140,7 +140,7 @@ func getCredentialsFromFile() msgs.BasicAuthCredentials { } } - //look in etc for pgouser file + // look in etc for pgouser file if !found { fullPath = "/etc/pgo/pgouser" dat, err := ioutil.ReadFile(fullPath) @@ -210,7 +210,7 @@ func GetTLSTransport() (*http.Transport, error) { caCertPool = x509.NewCertPool() } else { if pool, err := x509.SystemCertPool(); err != nil { - return nil, fmt.Errorf("while loading System CA pool - %s", err) + return nil, fmt.Errorf("while loading System CA pool - %w", err) } else { caCertPool = pool } @@ -227,12 +227,12 @@ func GetTLSTransport() (*http.Transport, error) { // Open trust file and extend trust pool if trustFile, err := os.Open(caCertPath); err != nil { - newErr := fmt.Errorf("unable to load TLS trust from %s - [%v]", caCertPath, err) + newErr := fmt.Errorf("unable to load TLS trust from %s - %w", caCertPath, err) return nil, newErr } else { err = tlsutil.ExtendTrust(caCertPool, trustFile) if err != nil { - newErr := fmt.Errorf("error reading %s - %v", caCertPath, err) + newErr := fmt.Errorf("error reading %s - %w", caCertPath, err) return nil, newErr } trustFile.Close() @@ -258,10 +258,11 @@ func GetTLSTransport() (*http.Transport, error) { certPair, err := tls.LoadX509KeyPair(clientCertPath, clientKeyPath) if err != nil { - return nil, fmt.Errorf("client certificate/key loading: %s", err) + return nil, fmt.Errorf("client certificate/key loading: %w", err) } // create a Transport object for use by the HTTP client + // #nosec: G402 return &http.Transport{ TLSClientConfig: &tls.Config{ RootCAs: caCertPool, diff --git a/cmd/pgo/cmd/backrest.go b/cmd/pgo/cmd/backrest.go index 46d843206e..2b149097cf 100644 --- a/cmd/pgo/cmd/backrest.go +++ b/cmd/pgo/cmd/backrest.go @@ -57,7 +57,6 @@ func createBackrestBackup(args []string, ns string) { fmt.Println("No clusters found.") return } - } // showBackrest .... @@ -84,8 +83,8 @@ func showBackrest(args []string, ns string) { log.Debugf("response = %v", response) log.Debugf("len of items = %d", len(response.Items)) - for _, backup := range response.Items { - printBackrest(&backup) + for i := range response.Items { + printBackrest(&response.Items[i]) } } } diff --git a/cmd/pgo/cmd/cat.go b/cmd/pgo/cmd/cat.go index 08b3a93109..7afe28126f 100644 --- a/cmd/pgo/cmd/cat.go +++ b/cmd/pgo/cmd/cat.go @@ -42,7 +42,6 @@ var catCmd = &cobra.Command{ } else { cat(args, Namespace) } - }, } @@ -58,7 +57,6 @@ func cat(args []string, ns string) { request.Args = args request.Namespace = ns response, err := api.Cat(httpclient, &SessionCredentials, request) - if err != nil { fmt.Println("Error: " + err.Error()) os.Exit(2) @@ -77,5 +75,4 @@ func cat(args []string, ns string) { fmt.Println("No clusters found.") return } - } diff --git a/cmd/pgo/cmd/cluster.go b/cmd/pgo/cmd/cluster.go index 5c8a318fc3..4dc16a0fdc 100644 --- a/cmd/pgo/cmd/cluster.go +++ b/cmd/pgo/cmd/cluster.go @@ -75,7 +75,6 @@ func deleteCluster(args []string, ns string) { for _, arg := range args { r.Clustername = arg response, err := api.DeleteCluster(httpclient, &r, &SessionCredentials) - if err != nil { fmt.Println("Error: " + err.Error()) os.Exit(2) @@ -90,12 +89,10 @@ func deleteCluster(args []string, ns string) { } } - } // showCluster ... func showCluster(args []string, ns string) { - log.Debugf("showCluster called %v", args) if OutputFormat != "" { @@ -149,12 +146,11 @@ func showCluster(args []string, ns string) { return } - for _, clusterDetail := range response.Results { - printCluster(&clusterDetail) + for i := range response.Results { + printCluster(&response.Results[i]) } } - } // printCluster @@ -173,7 +169,7 @@ func printCluster(detail *msgs.ShowClusterDetail) { podStr := fmt.Sprintf("%spod : %s (%s) on %s (%s) %s", TreeBranch, pod.Name, string(pod.Phase), pod.NodeName, pod.ReadyStatus, podType) fmt.Println(podStr) for _, pvc := range pod.PVC { - fmt.Println(fmt.Sprintf("%spvc: %s (%s)", TreeBranch+TreeBranch, pvc.Name, pvc.Capacity)) + fmt.Printf("%spvc: %s (%s)\n", TreeBranch+TreeBranch, pvc.Name, pvc.Capacity) } } @@ -237,7 +233,6 @@ func printCluster(detail *msgs.ShowClusterDetail) { fmt.Printf("%s=%s ", k, v) } fmt.Println("") - } func printPolicies(d *msgs.ShowClusterDeployment) { @@ -699,7 +694,6 @@ func updateCluster(args []string, ns string) { } response, err := api.UpdateCluster(httpclient, &r, &SessionCredentials) - if err != nil { fmt.Println("Error: " + err.Error()) os.Exit(2) @@ -712,5 +706,4 @@ func updateCluster(args []string, ns string) { } else { fmt.Println("Error: " + response.Status.Msg) } - } diff --git a/cmd/pgo/cmd/config.go b/cmd/pgo/cmd/config.go index b8e4d89d0f..0d5f4f4335 100644 --- a/cmd/pgo/cmd/config.go +++ b/cmd/pgo/cmd/config.go @@ -28,11 +28,9 @@ import ( ) func showConfig(args []string, ns string) { - log.Debugf("showConfig called %v", args) response, err := api.ShowConfig(httpclient, &SessionCredentials, ns) - if err != nil { fmt.Println("Error: " + err.Error()) os.Exit(2) @@ -60,5 +58,4 @@ func showConfig(args []string, ns string) { } fmt.Println(string(y)) - } diff --git a/cmd/pgo/cmd/create.go b/cmd/pgo/cmd/create.go index 25dd1119b9..eaa60e43fa 100644 --- a/cmd/pgo/cmd/create.go +++ b/cmd/pgo/cmd/create.go @@ -23,51 +23,53 @@ import ( "github.com/spf13/cobra" ) -var ClusterReplicaCount int -var ManagedUser bool -var AllNamespaces bool -var BackrestStorageConfig, ReplicaStorageConfig, StorageConfig string -var CustomConfig string -var ArchiveFlag, DisableAutofailFlag, EnableAutofailFlag, PgbouncerFlag, MetricsFlag, BadgerFlag bool -var BackrestRestoreFrom string -var CCPImage string -var CCPImageTag string -var CCPImagePrefix string -var PGOImagePrefix string -var Database string -var Password string -var SecretFrom string -var PoliciesFlag, PolicyFile, PolicyURL string -var UserLabels string -var Tablespaces []string -var ServiceType string -var Schedule string -var ScheduleOptions string -var ScheduleType string -var SchedulePolicy string -var ScheduleDatabase string -var ScheduleSecret string -var PGBackRestType string -var Secret string -var PgouserPassword, PgouserRoles, PgouserNamespaces string -var Permissions string -var PodAntiAffinity string -var PodAntiAffinityPgBackRest string -var PodAntiAffinityPgBouncer string -var SyncReplication bool -var BackrestConfig string -var BackrestS3Key string -var BackrestS3KeySecret string -var BackrestS3Bucket string -var BackrestS3Endpoint string -var BackrestS3Region string -var BackrestS3URIStyle string -var BackrestS3VerifyTLS bool -var PVCSize string -var BackrestPVCSize string -var WALStorageConfig string -var WALPVCSize string -var RestoreFrom string +var ( + ClusterReplicaCount int + ManagedUser bool + AllNamespaces bool + BackrestStorageConfig, ReplicaStorageConfig, StorageConfig string + CustomConfig string + ArchiveFlag, DisableAutofailFlag, EnableAutofailFlag, PgbouncerFlag, MetricsFlag, BadgerFlag bool + BackrestRestoreFrom string + CCPImage string + CCPImageTag string + CCPImagePrefix string + PGOImagePrefix string + Database string + Password string + SecretFrom string + PoliciesFlag, PolicyFile, PolicyURL string + UserLabels string + Tablespaces []string + ServiceType string + Schedule string + ScheduleOptions string + ScheduleType string + SchedulePolicy string + ScheduleDatabase string + ScheduleSecret string + PGBackRestType string + Secret string + PgouserPassword, PgouserRoles, PgouserNamespaces string + Permissions string + PodAntiAffinity string + PodAntiAffinityPgBackRest string + PodAntiAffinityPgBouncer string + SyncReplication bool + BackrestConfig string + BackrestS3Key string + BackrestS3KeySecret string + BackrestS3Bucket string + BackrestS3Endpoint string + BackrestS3Region string + BackrestS3URIStyle string + BackrestS3VerifyTLS bool + PVCSize string + BackrestPVCSize string + WALStorageConfig string + WALPVCSize string + RestoreFrom string +) // group the annotation requests var ( @@ -243,7 +245,6 @@ var createPgAdminCmd = &cobra.Command{ pgo create pgadmin mycluster`, Run: func(cmd *cobra.Command, args []string) { - if Namespace == "" { Namespace = PGONamespace } @@ -265,7 +266,6 @@ var createPgbouncerCmd = &cobra.Command{ pgo create pgbouncer mycluster`, Run: func(cmd *cobra.Command, args []string) { - if Namespace == "" { Namespace = PGONamespace } @@ -293,7 +293,6 @@ var createScheduleCmd = &cobra.Command{ pgo create schedule --schedule="* * * * *" --schedule-type=pgbackrest --pgbackrest-backup-type=full mycluster`, Run: func(cmd *cobra.Command, args []string) { - if Namespace == "" { Namespace = PGONamespace } @@ -317,7 +316,6 @@ var createUserCmd = &cobra.Command{ pgo create user --username=someuser -selector=name=mycluster --managed pgo create user --username=user1 --selector=name=mycluster`, Run: func(cmd *cobra.Command, args []string) { - if Namespace == "" { Namespace = PGONamespace } diff --git a/cmd/pgo/cmd/delete.go b/cmd/pgo/cmd/delete.go index df21d71ea4..86a6a68c7c 100644 --- a/cmd/pgo/cmd/delete.go +++ b/cmd/pgo/cmd/delete.go @@ -137,14 +137,14 @@ func init() { // instructs that any backups associated with a cluster should be deleted deleteClusterCmd.Flags().BoolVarP(&deleteBackups, "delete-backups", "b", false, "Causes the backups for specified cluster to be removed permanently.") - deleteClusterCmd.Flags().MarkDeprecated("delete-backups", + _ = deleteClusterCmd.Flags().MarkDeprecated("delete-backups", "Backups are deleted by default. If you would like to keep your backups, use the --keep-backups flag") // "pgo delete cluster --delete-data" // "pgo delete cluster -d" // instructs that the PostgreSQL cluster data should be deleted deleteClusterCmd.Flags().BoolVarP(&DeleteData, "delete-data", "d", false, "Causes the data for specified cluster to be removed permanently.") - deleteClusterCmd.Flags().MarkDeprecated("delete-data", + _ = deleteClusterCmd.Flags().MarkDeprecated("delete-data", "Data is deleted by default. You can preserve your data by keeping your backups with the --keep-backups flag") // "pgo delete cluster --keep-backups" // instructs that any backups associated with a cluster should be kept and not deleted @@ -557,7 +557,7 @@ var deleteUserCmd = &cobra.Command{ if Namespace == "" { Namespace = PGONamespace } - if len(args) == 0 && AllFlag == false && Selector == "" { + if len(args) == 0 && !AllFlag && Selector == "" { fmt.Println("Error: --all, --selector, or a list of clusters is required for this command") } else { if util.AskForConfirmation(NoPrompt, "") { diff --git a/cmd/pgo/cmd/df.go b/cmd/pgo/cmd/df.go index 16f764830a..bad4301ceb 100644 --- a/cmd/pgo/cmd/df.go +++ b/cmd/pgo/cmd/df.go @@ -112,7 +112,6 @@ func init() { dfCmd.Flags().BoolVar(&AllFlag, "all", false, "Get disk utilization for all managed clusters") dfCmd.Flags().StringVarP(&OutputFormat, "output", "o", "", `The output format. Supported types are: "json"`) dfCmd.Flags().StringVarP(&Selector, "selector", "s", "", "The selector to use for cluster filtering.") - } // getPVCType returns a "human readable" form of the PVC @@ -250,7 +249,6 @@ func showDf(namespace, selector string) { // make the request response, err := api.ShowDf(httpclient, &SessionCredentials, request) - // if there is an error, or the response code is not ok, print the error and // exit if err != nil { diff --git a/cmd/pgo/cmd/failover.go b/cmd/pgo/cmd/failover.go index 8b2b6ac8c2..1459c659ae 100644 --- a/cmd/pgo/cmd/failover.go +++ b/cmd/pgo/cmd/failover.go @@ -53,7 +53,6 @@ var failoverCmd = &cobra.Command{ fmt.Println("Aborting...") } } - }, } @@ -63,7 +62,6 @@ func init() { failoverCmd.Flags().BoolVarP(&Query, "query", "", false, "Prints the list of failover candidates.") failoverCmd.Flags().BoolVar(&NoPrompt, "no-prompt", false, "No command line confirmation.") failoverCmd.Flags().StringVarP(&Target, "target", "", "", "The replica target which the failover will occur on.") - } // createFailover .... @@ -77,7 +75,6 @@ func createFailover(args []string, ns string) { request.ClientVersion = msgs.PGO_VERSION response, err := api.CreateFailover(httpclient, &SessionCredentials, request) - if err != nil { fmt.Println("Error: " + err.Error()) os.Exit(2) @@ -91,7 +88,6 @@ func createFailover(args []string, ns string) { fmt.Println("Error: " + response.Status.Msg) os.Exit(2) } - } // queryFailover is a helper function to return the user information about the diff --git a/cmd/pgo/cmd/flags.go b/cmd/pgo/cmd/flags.go index bb831e4006..28afacc651 100644 --- a/cmd/pgo/cmd/flags.go +++ b/cmd/pgo/cmd/flags.go @@ -15,7 +15,7 @@ package cmd limitations under the License. */ -//flags used by more than 1 command +// flags used by more than 1 command var DeleteData bool // KeepData, If set to "true", indicates that cluster data should be stored @@ -24,29 +24,39 @@ var KeepData bool var Query bool -var Target string -var Targets []string - -var OutputFormat string -var Labelselector string -var DebugFlag bool -var Selector string -var DryRun bool -var ScheduleName string -var NodeLabel string - -var BackupType string -var RestoreType string -var BackupOpts string -var BackrestStorageType string - -var RED func(a ...interface{}) string -var YELLOW func(a ...interface{}) string -var GREEN func(a ...interface{}) string - -var Namespace string -var PGONamespace string -var APIServerURL string -var PGO_CA_CERT, PGO_CLIENT_CERT, PGO_CLIENT_KEY string -var PGO_DISABLE_TLS bool -var EXCLUDE_OS_TRUST bool +var ( + Target string + Targets []string +) + +var ( + OutputFormat string + Labelselector string + DebugFlag bool + Selector string + DryRun bool + ScheduleName string + NodeLabel string +) + +var ( + BackupType string + RestoreType string + BackupOpts string + BackrestStorageType string +) + +var ( + RED func(a ...interface{}) string + YELLOW func(a ...interface{}) string + GREEN func(a ...interface{}) string +) + +var ( + Namespace string + PGONamespace string + APIServerURL string + PGO_CA_CERT, PGO_CLIENT_CERT, PGO_CLIENT_KEY string + PGO_DISABLE_TLS bool + EXCLUDE_OS_TRUST bool +) diff --git a/cmd/pgo/cmd/label.go b/cmd/pgo/cmd/label.go index db2253d8a9..bd794e706b 100644 --- a/cmd/pgo/cmd/label.go +++ b/cmd/pgo/cmd/label.go @@ -25,9 +25,11 @@ import ( "github.com/spf13/cobra" ) -var LabelCmdLabel string -var LabelMap map[string]string -var DeleteLabel bool +var ( + LabelCmdLabel string + LabelMap map[string]string + DeleteLabel bool +) var labelCmd = &cobra.Command{ Use: "label", @@ -61,7 +63,6 @@ func init() { labelCmd.Flags().StringVarP(&Selector, "selector", "s", "", "The selector to use for cluster filtering.") labelCmd.Flags().StringVarP(&LabelCmdLabel, "label", "", "", "The new label to apply for any selected or specified clusters.") labelCmd.Flags().BoolVarP(&DryRun, "dry-run", "", false, "Shows the clusters that the label would be applied to, without labelling them.") - } func labelClusters(clusters []string, ns string) { @@ -100,7 +101,6 @@ func labelClusters(clusters []string, ns string) { fmt.Println("Error: " + response.Status.Msg) os.Exit(2) } - } // deleteLabel ... @@ -127,5 +127,4 @@ func deleteLabel(args []string, ns string) { } else { fmt.Println("Error: " + response.Status.Msg) } - } diff --git a/cmd/pgo/cmd/namespace.go b/cmd/pgo/cmd/namespace.go index baa0f9ce92..e095328360 100644 --- a/cmd/pgo/cmd/namespace.go +++ b/cmd/pgo/cmd/namespace.go @@ -54,7 +54,7 @@ func showNamespace(args []string) { r.Args = nsList r.AllFlag = AllFlag - if len(nsList) == 0 && AllFlag == false { + if len(nsList) == 0 && !AllFlag { fmt.Println("Error: namespace args or --all is required") os.Exit(2) } @@ -62,7 +62,6 @@ func showNamespace(args []string) { log.Debugf("showNamespace called %v", nsList) response, err := api.ShowNamespace(httpclient, &SessionCredentials, &r) - if err != nil { fmt.Println("Error: " + err.Error()) os.Exit(2) @@ -107,7 +106,6 @@ func showNamespace(args []string) { fmt.Printf("%s", accessible) fmt.Printf("%s\n", iAccessible) } - } func createNamespace(args []string, ns string) { @@ -167,8 +165,8 @@ func deleteNamespace(args []string, ns string) { } else { fmt.Println("Error: " + response.Status.Msg) } - } + func updateNamespace(args []string) { var err error @@ -182,7 +180,6 @@ func updateNamespace(args []string) { r.ClientVersion = msgs.PGO_VERSION response, err := api.UpdateNamespace(httpclient, r, &SessionCredentials) - if err != nil { fmt.Println("Error: " + err.Error()) os.Exit(2) @@ -194,5 +191,4 @@ func updateNamespace(args []string) { fmt.Println("Error: " + response.Status.Msg) os.Exit(2) } - } diff --git a/cmd/pgo/cmd/pgadmin.go b/cmd/pgo/cmd/pgadmin.go index 8864de9abe..0d034f1710 100644 --- a/cmd/pgo/cmd/pgadmin.go +++ b/cmd/pgo/cmd/pgadmin.go @@ -47,7 +47,6 @@ func createPgAdmin(args []string, ns string) { } response, err := api.CreatePgAdmin(httpclient, &SessionCredentials, &request) - if err != nil { fmt.Println("Error: " + err.Error()) os.Exit(1) @@ -95,7 +94,6 @@ func deletePgAdmin(args []string, ns string) { fmt.Println("Error: " + response.Status.Msg) os.Exit(1) } - } // makeShowPgAdminInterface returns an interface slice of the available values diff --git a/cmd/pgo/cmd/pgbouncer.go b/cmd/pgo/cmd/pgbouncer.go index 9450623bf1..f9096e4419 100644 --- a/cmd/pgo/cmd/pgbouncer.go +++ b/cmd/pgo/cmd/pgbouncer.go @@ -52,7 +52,6 @@ var PgBouncerReplicas int32 var PgBouncerUninstall bool func createPgbouncer(args []string, ns string) { - if Selector == "" && len(args) == 0 { fmt.Println("Error: The --selector flag is required.") return @@ -92,7 +91,6 @@ func createPgbouncer(args []string, ns string) { } response, err := api.CreatePgbouncer(httpclient, &SessionCredentials, &request) - if err != nil { fmt.Println("Error: " + err.Error()) os.Exit(1) @@ -115,7 +113,6 @@ func createPgbouncer(args []string, ns string) { } func deletePgbouncer(args []string, ns string) { - if Selector == "" && len(args) == 0 { fmt.Println("Error: The --selector flag or a cluster name is required.") return @@ -144,7 +141,6 @@ func deletePgbouncer(args []string, ns string) { fmt.Println("Error: " + response.Status.Msg) os.Exit(2) } - } // makeShowPgBouncerInterface returns an interface slice of the available values @@ -332,7 +328,6 @@ func showPgBouncer(namespace string, clusterNames []string) { // and make the API request! response, err := api.ShowPgBouncer(httpclient, &SessionCredentials, request) - // if there is a bona-fide error, log and exit if err != nil { fmt.Println("Error:", err.Error()) @@ -396,7 +391,6 @@ func updatePgBouncer(namespace string, clusterNames []string) { // and make the API request! response, err := api.UpdatePgBouncer(httpclient, &SessionCredentials, request) - // if there is a bona-fide error, log and exit if err != nil { fmt.Println("Error:", err.Error()) diff --git a/cmd/pgo/cmd/pgdump.go b/cmd/pgo/cmd/pgdump.go index b9b64046bc..0ca8dfd637 100644 --- a/cmd/pgo/cmd/pgdump.go +++ b/cmd/pgo/cmd/pgdump.go @@ -57,7 +57,6 @@ func createpgDumpBackup(args []string, ns string) { fmt.Println("No clusters found.") return } - } // pgDump .... @@ -84,8 +83,8 @@ func showpgDump(args []string, ns string) { log.Debugf("response = %v", response) log.Debugf("len of items = %d", len(response.BackupList.Items)) - for _, backup := range response.BackupList.Items { - printDumpCRD(&backup) + for i := range response.BackupList.Items { + printDumpCRD(&response.BackupList.Items[i]) } } } @@ -105,5 +104,4 @@ func printDumpCRD(result *msgs.Pgbackup) { fmt.Printf("%s%s\n", TreeBranch, "Backup User Secret:\t"+result.BackupUserSecret) fmt.Printf("%s%s\n", TreeTrunk, "Backup Port:\t"+result.BackupPort) fmt.Printf("%s%s\n", TreeTrunk, "Backup Opts:\t"+result.BackupOpts) - } diff --git a/cmd/pgo/cmd/pgorole.go b/cmd/pgo/cmd/pgorole.go index 381542d3b4..180459d2ef 100644 --- a/cmd/pgo/cmd/pgorole.go +++ b/cmd/pgo/cmd/pgorole.go @@ -45,7 +45,6 @@ func updatePgorole(args []string, ns string) { r.ClientVersion = msgs.PGO_VERSION response, err := api.UpdatePgorole(httpclient, &SessionCredentials, r) - if err != nil { fmt.Println("Error: " + err.Error()) os.Exit(2) @@ -57,11 +56,9 @@ func updatePgorole(args []string, ns string) { fmt.Println("Error: " + response.Status.Msg) os.Exit(2) } - } func showPgorole(args []string, ns string) { - r := new(msgs.ShowPgoroleRequest) r.PgoroleName = args r.Namespace = ns @@ -74,7 +71,6 @@ func showPgorole(args []string, ns string) { } response, err := api.ShowPgorole(httpclient, &SessionCredentials, r) - if err != nil { fmt.Println("Error: " + err.Error()) os.Exit(2) @@ -97,11 +93,9 @@ func showPgorole(args []string, ns string) { fmt.Println("pgorole : " + pgorole.Name) fmt.Println("permissions : " + pgorole.Permissions) } - } func createPgorole(args []string, ns string) { - if Permissions == "" { fmt.Println("Error: permissions flag is required.") return @@ -112,7 +106,7 @@ func createPgorole(args []string, ns string) { return } var err error - //create the request + // create the request r := new(msgs.CreatePgoroleRequest) r.PgoroleName = args[0] r.PgorolePermissions = Permissions @@ -133,11 +127,9 @@ func createPgorole(args []string, ns string) { fmt.Println("Error: " + response.Status.Msg) os.Exit(2) } - } func deletePgorole(args []string, ns string) { - log.Debugf("deletePgorole called %v", args) r := msgs.DeletePgoroleRequest{} @@ -165,5 +157,4 @@ func deletePgorole(args []string, ns string) { } else { fmt.Println("Error: " + response.Status.Msg) } - } diff --git a/cmd/pgo/cmd/pgouser.go b/cmd/pgo/cmd/pgouser.go index 9bfc58167f..31ea66b316 100644 --- a/cmd/pgo/cmd/pgouser.go +++ b/cmd/pgo/cmd/pgouser.go @@ -47,7 +47,6 @@ func updatePgouser(args []string, ns string) { r.ClientVersion = msgs.PGO_VERSION response, err := api.UpdatePgouser(httpclient, &SessionCredentials, r) - if err != nil { fmt.Println("Error: " + err.Error()) os.Exit(2) @@ -59,11 +58,9 @@ func updatePgouser(args []string, ns string) { fmt.Println("Error: " + response.Status.Msg) os.Exit(2) } - } func showPgouser(args []string, ns string) { - r := new(msgs.ShowPgouserRequest) r.PgouserName = args r.Namespace = ns @@ -76,7 +73,6 @@ func showPgouser(args []string, ns string) { } response, err := api.ShowPgouser(httpclient, &SessionCredentials, r) - if err != nil { fmt.Println("Error: " + err.Error()) os.Exit(2) @@ -100,11 +96,9 @@ func showPgouser(args []string, ns string) { fmt.Printf("roles : %v\n", pgouser.Role) fmt.Printf("namespaces : %v\n", pgouser.Namespace) } - } func createPgouser(args []string, ns string) { - if PgouserPassword == "" { fmt.Println("Error: pgouser-password flag is required.") return @@ -128,7 +122,7 @@ func createPgouser(args []string, ns string) { return } var err error - //create the request + // create the request r := new(msgs.CreatePgouserRequest) r.PgouserName = args[0] r.PgouserPassword = PgouserPassword @@ -152,11 +146,9 @@ func createPgouser(args []string, ns string) { fmt.Println("Error: " + response.Status.Msg) os.Exit(2) } - } func deletePgouser(args []string, ns string) { - log.Debugf("deletePgouser called %v", args) r := msgs.DeletePgouserRequest{} @@ -184,5 +176,4 @@ func deletePgouser(args []string, ns string) { } else { fmt.Println("Error: " + response.Status.Msg) } - } diff --git a/cmd/pgo/cmd/policy.go b/cmd/pgo/cmd/policy.go index 90d8f04f26..96a858efc9 100644 --- a/cmd/pgo/cmd/policy.go +++ b/cmd/pgo/cmd/policy.go @@ -58,7 +58,6 @@ func init() { applyCmd.Flags().StringVarP(&Selector, "selector", "s", "", "The selector to use for cluster filtering.") applyCmd.Flags().BoolVarP(&DryRun, "dry-run", "", false, "Shows the clusters that the label would be applied to, without labelling them.") - } func applyPolicy(args []string, ns string) { @@ -82,7 +81,6 @@ func applyPolicy(args []string, ns string) { r.ClientVersion = msgs.PGO_VERSION response, err := api.ApplyPolicy(httpclient, &SessionCredentials, r) - if err != nil { fmt.Println("Error: " + err.Error()) os.Exit(2) @@ -104,10 +102,9 @@ func applyPolicy(args []string, ns string) { fmt.Println("Error: " + response.Status.Msg) os.Exit(2) } - } -func showPolicy(args []string, ns string) { +func showPolicy(args []string, ns string) { r := new(msgs.ShowPolicyRequest) r.Selector = Selector r.Namespace = ns @@ -122,7 +119,6 @@ func showPolicy(args []string, ns string) { r.Policyname = v response, err := api.ShowPolicy(httpclient, &SessionCredentials, r) - if err != nil { fmt.Println("Error: " + err.Error()) os.Exit(2) @@ -148,17 +144,15 @@ func showPolicy(args []string, ns string) { fmt.Println(TreeTrunk + "sql : " + policy.Spec.SQL) } } - } func createPolicy(args []string, ns string) { - if len(args) == 0 { fmt.Println("Error: A poliicy name argument is required.") return } var err error - //create the request + // create the request r := new(msgs.CreatePolicyRequest) r.Name = args[0] r.Namespace = ns @@ -190,7 +184,6 @@ func createPolicy(args []string, ns string) { fmt.Println("Error: " + response.Status.Msg) os.Exit(2) } - } func getPolicyString(filename string) (string, error) { @@ -205,7 +198,6 @@ func getPolicyString(filename string) (string, error) { } func deletePolicy(args []string, ns string) { - log.Debugf("deletePolicy called %v", args) r := msgs.DeletePolicyRequest{} diff --git a/cmd/pgo/cmd/pvc.go b/cmd/pgo/cmd/pvc.go index 856e134533..3991901442 100644 --- a/cmd/pgo/cmd/pvc.go +++ b/cmd/pgo/cmd/pvc.go @@ -34,22 +34,20 @@ func showPVC(args []string, ns string) { r.ClientVersion = msgs.PGO_VERSION if AllFlag { - //special case to just list all the PVCs + // special case to just list all the PVCs r.ClusterName = "" printPVC(&r) } else { - //args are a list of pvc names...for this case show details + // args are a list of pvc names...for this case show details for _, arg := range args { r.ClusterName = arg log.Debugf("show pvc called for %s", arg) printPVC(&r) } } - } func printPVC(r *msgs.ShowPVCRequest) { - response, err := api.ShowPVC(httpclient, r, &SessionCredentials) log.Debugf("response = %v", response) @@ -74,5 +72,4 @@ func printPVC(r *msgs.ShowPVCRequest) { for _, v := range response.Results { fmt.Printf("%-20s\t%-30s\n", v.ClusterName, v.PVCName) } - } diff --git a/cmd/pgo/cmd/reload.go b/cmd/pgo/cmd/reload.go index 415c31a567..9d8b85b2b4 100644 --- a/cmd/pgo/cmd/reload.go +++ b/cmd/pgo/cmd/reload.go @@ -27,7 +27,7 @@ import ( "github.com/spf13/cobra" ) -//unused but coming soon to a theatre near you +// unused but coming soon to a theatre near you var ConfigMapName string var reloadCmd = &cobra.Command{ @@ -50,7 +50,6 @@ var reloadCmd = &cobra.Command{ fmt.Println("Aborting...") } } - }, } @@ -59,7 +58,6 @@ func init() { reloadCmd.Flags().StringVarP(&Selector, "selector", "s", "", "The selector to use for cluster filtering.") reloadCmd.Flags().BoolVar(&NoPrompt, "no-prompt", false, "No command line confirmation.") - } // reload .... @@ -71,7 +69,6 @@ func reload(args []string, ns string) { request.Selector = Selector request.Namespace = ns response, err := api.Reload(httpclient, &SessionCredentials, request) - if err != nil { fmt.Println("Error: " + err.Error()) os.Exit(2) @@ -95,5 +92,4 @@ func reload(args []string, ns string) { fmt.Println("No clusters found.") return } - } diff --git a/cmd/pgo/cmd/restart.go b/cmd/pgo/cmd/restart.go index 02d80a1229..5349a1ff7e 100644 --- a/cmd/pgo/cmd/restart.go +++ b/cmd/pgo/cmd/restart.go @@ -47,7 +47,6 @@ var restartCmd = &cobra.Command{ And use the 'query' flag obtain a list of all instances within the cluster: pgo restart mycluster --query`, Run: func(cmd *cobra.Command, args []string) { - if OutputFormat != "" { if OutputFormat != "json" { fmt.Println("Error: ", "json is the only supported --output format value") @@ -77,7 +76,6 @@ var restartCmd = &cobra.Command{ } func init() { - RootCmd.AddCommand(restartCmd) restartCmd.Flags().BoolVar(&NoPrompt, "no-prompt", false, "No command line confirmation.") @@ -89,7 +87,6 @@ func init() { // restart sends a request to restart a PG cluster or one or more instances within it. func restart(clusterName, namespace string) { - log.Debugf("restart called %v", clusterName) request := new(msgs.RestartRequest) @@ -141,7 +138,6 @@ func restart(clusterName, namespace string) { // instances (the primary and all replicas) within a cluster. This is useful when the user // would like to specify one or more instances for a restart using the "--target" flag. func queryRestart(args []string, namespace string) { - log.Debugf("queryRestart called %v", args) for _, clusterName := range args { diff --git a/cmd/pgo/cmd/restore.go b/cmd/pgo/cmd/restore.go index 4906bb7510..aa3b4bb725 100644 --- a/cmd/pgo/cmd/restore.go +++ b/cmd/pgo/cmd/restore.go @@ -28,8 +28,10 @@ import ( "github.com/spf13/cobra" ) -var PITRTarget string -var BackupPath, BackupPVC string +var ( + PITRTarget string + BackupPath, BackupPVC string +) var restoreCmd = &cobra.Command{ Use: "restore", @@ -54,7 +56,6 @@ var restoreCmd = &cobra.Command{ fmt.Println("Aborting...") } } - }, } @@ -123,5 +124,4 @@ func restore(args []string, ns string) { fmt.Println("No clusters found.") return } - } diff --git a/cmd/pgo/cmd/root.go b/cmd/pgo/cmd/root.go index 249e0a8f91..3cd416c784 100644 --- a/cmd/pgo/cmd/root.go +++ b/cmd/pgo/cmd/root.go @@ -45,11 +45,9 @@ func Execute() { log.Debug(err.Error()) os.Exit(-1) } - } func init() { - cobra.OnInitialize(initConfig) log.Debug("init called") GREEN = color.New(color.FgGreen).SprintFunc() @@ -68,7 +66,6 @@ func init() { RootCmd.PersistentFlags().BoolVar(&PGO_DISABLE_TLS, "disable-tls", false, "Disable TLS authentication to the Postgres Operator.") RootCmd.PersistentFlags().BoolVar(&EXCLUDE_OS_TRUST, "exclude-os-trust", defExclOSTrust, "Exclude CA certs from OS default trust store") RootCmd.PersistentFlags().BoolVar(&DebugFlag, "debug", false, "Enable additional output for debugging.") - } func initConfig() { @@ -115,10 +112,11 @@ func initConfig() { func generateBashCompletion() { log.Debugf("generating bash completion script") + // #nosec: G303 file, err2 := os.Create("/tmp/pgo-bash-completion.out") if err2 != nil { fmt.Println("Error: ", err2.Error()) } defer file.Close() - RootCmd.GenBashCompletion(file) + _ = RootCmd.GenBashCompletion(file) } diff --git a/cmd/pgo/cmd/scale.go b/cmd/pgo/cmd/scale.go index e25709bc0e..0fd9bbdb16 100644 --- a/cmd/pgo/cmd/scale.go +++ b/cmd/pgo/cmd/scale.go @@ -65,12 +65,10 @@ func init() { } func scaleCluster(args []string, ns string) { - for _, arg := range args { log.Debugf(" %s ReplicaCount is %d", arg, ReplicaCount) response, err := api.ScaleCluster(httpclient, arg, ReplicaCount, StorageConfig, NodeLabel, CCPImageTag, ServiceType, &SessionCredentials, ns) - if err != nil { fmt.Println("Error: " + err.Error()) os.Exit(2) diff --git a/cmd/pgo/cmd/scaledown.go b/cmd/pgo/cmd/scaledown.go index be60d27ed7..7b49350599 100644 --- a/cmd/pgo/cmd/scaledown.go +++ b/cmd/pgo/cmd/scaledown.go @@ -66,7 +66,7 @@ func init() { scaledownCmd.Flags().StringVarP(&Target, "target", "", "", "The replica to target for scaling down") scaledownCmd.Flags().BoolVarP(&DeleteData, "delete-data", "d", true, "Causes the data for the scaled down replica to be removed permanently.") - scaledownCmd.Flags().MarkDeprecated("delete-data", "Data is deleted by default.") + _ = scaledownCmd.Flags().MarkDeprecated("delete-data", "Data is deleted by default.") scaledownCmd.Flags().BoolVar(&KeepData, "keep-data", false, "Causes data for the scale down replica to *not* be deleted") scaledownCmd.Flags().BoolVar(&NoPrompt, "no-prompt", false, "No command line confirmation.") @@ -76,7 +76,6 @@ func init() { // available replicas that can be scaled down. This is called when the "--query" // flag is specified func queryCluster(args []string, ns string) { - // iterate through the clusters and output information about each one for _, arg := range args { @@ -134,7 +133,6 @@ func queryCluster(args []string, ns string) { } func scaleDownCluster(clusterName, ns string) { - // determine if the data should be deleted. The modern flag for handling this // is "KeepData" which defaults to "false". We will honor the "DeleteData" // flag (which defaults to "true"), but this will be removed in a future @@ -143,7 +141,6 @@ func scaleDownCluster(clusterName, ns string) { response, err := api.ScaleDownCluster(httpclient, clusterName, Target, deleteData, &SessionCredentials, ns) - if err != nil { fmt.Println("Error: ", err.Error()) return @@ -156,5 +153,4 @@ func scaleDownCluster(clusterName, ns string) { } else { fmt.Println("Error: " + response.Status.Msg) } - } diff --git a/cmd/pgo/cmd/schedule.go b/cmd/pgo/cmd/schedule.go index c91018c016..86a06aa8ae 100644 --- a/cmd/pgo/cmd/schedule.go +++ b/cmd/pgo/cmd/schedule.go @@ -81,7 +81,6 @@ func createSchedule(args []string, ns string) { } response, err := api.CreateSchedule(httpclient, &SessionCredentials, r) - if err != nil { fmt.Println("Error: " + response.Status.Msg) os.Exit(2) @@ -100,7 +99,6 @@ func createSchedule(args []string, ns string) { fmt.Println("No clusters found.") return } - } func deleteSchedule(args []string, ns string) { @@ -124,7 +122,6 @@ func deleteSchedule(args []string, ns string) { } response, err := api.DeleteSchedule(httpclient, &SessionCredentials, r) - if err != nil { fmt.Println("Error: " + response.Status.Msg) os.Exit(2) @@ -143,7 +140,6 @@ func deleteSchedule(args []string, ns string) { fmt.Println("No schedules found.") return } - } func showSchedule(args []string, ns string) { @@ -169,7 +165,6 @@ func showSchedule(args []string, ns string) { } response, err := api.ShowSchedule(httpclient, &SessionCredentials, r) - if err != nil { fmt.Println("Error: " + response.Status.Msg) os.Exit(2) diff --git a/cmd/pgo/cmd/show.go b/cmd/pgo/cmd/show.go index b42ba2a7d9..96a312b733 100644 --- a/cmd/pgo/cmd/show.go +++ b/cmd/pgo/cmd/show.go @@ -22,8 +22,10 @@ import ( "github.com/spf13/cobra" ) -const TreeBranch = "\t" -const TreeTrunk = "\t" +const ( + TreeBranch = "\t" + TreeTrunk = "\t" +) var AllFlag bool @@ -80,7 +82,6 @@ Valid resource types include: * user`) } } - }, } @@ -327,7 +328,7 @@ var ShowUserCmd = &cobra.Command{ if Namespace == "" { Namespace = PGONamespace } - if Selector == "" && AllFlag == false && len(args) == 0 { + if Selector == "" && !AllFlag && len(args) == 0 { fmt.Println("Error: --selector, --all, or cluster name()s required for this command") } else { showUser(args, Namespace) diff --git a/cmd/pgo/cmd/status.go b/cmd/pgo/cmd/status.go index f7cea5a946..28c930f226 100644 --- a/cmd/pgo/cmd/status.go +++ b/cmd/pgo/cmd/status.go @@ -33,11 +33,9 @@ func init() { RootCmd.AddCommand(statusCmd) statusCmd.Flags().StringVarP(&OutputFormat, "output", "o", "", "The output format. Currently, json is the only supported value.") - } func showStatus(args []string, ns string) { - log.Debugf("showStatus called %v", args) if OutputFormat != "" && OutputFormat != "json" { @@ -46,7 +44,6 @@ func showStatus(args []string, ns string) { } response, err := api.ShowStatus(httpclient, &SessionCredentials, ns) - if err != nil { fmt.Println("Error: " + err.Error()) os.Exit(2) @@ -67,11 +64,9 @@ func showStatus(args []string, ns string) { } printSummary(&response.Result) - } func printSummary(status *msgs.StatusDetail) { - WID := 25 fmt.Printf("%s%d\n", util.Rpad("Databases:", " ", WID), status.NumDatabases) fmt.Printf("%s%d\n", util.Rpad("Claims:", " ", WID), status.NumClaims) diff --git a/cmd/pgo/cmd/test.go b/cmd/pgo/cmd/test.go index a25ce23b9c..89da7b7b7f 100644 --- a/cmd/pgo/cmd/test.go +++ b/cmd/pgo/cmd/test.go @@ -57,11 +57,9 @@ func init() { testCmd.Flags().StringVarP(&Selector, "selector", "s", "", "The selector to use for cluster filtering.") testCmd.Flags().StringVarP(&OutputFormat, "output", "o", "", "The output format. Currently, json is the only supported value.") testCmd.Flags().BoolVar(&AllFlag, "all", false, "test all resources.") - } func showTest(args []string, ns string) { - log.Debugf("showCluster called %v", args) log.Debugf("selector is %s", Selector) @@ -110,7 +108,7 @@ func showTest(args []string, ns string) { for _, result := range response.Results { fmt.Println("") - fmt.Println(fmt.Sprintf("cluster : %s", result.ClusterName)) + fmt.Printf("cluster : %s\n", result.ClusterName) // first, print the test results for the endpoints, which make up // the services @@ -124,15 +122,15 @@ func showTest(args []string, ns string) { // prints out a set of test results func printTestResults(testName string, results []msgs.ClusterTestDetail) { // print out the header for this group of tests - fmt.Println(fmt.Sprintf("%s%s", TreeBranch, testName)) + fmt.Printf("%s%s\n", TreeBranch, testName) // iterate though the results and print them! for _, v := range results { fmt.Printf("%s%s%s (%s): ", TreeBranch, TreeBranch, v.InstanceType, v.Message) if v.Available { - fmt.Println(fmt.Sprintf("%s", GREEN("UP"))) + fmt.Println(GREEN("UP")) } else { - fmt.Println(fmt.Sprintf("%s", RED("DOWN"))) + fmt.Println(RED("DOWN")) } } } diff --git a/cmd/pgo/cmd/update.go b/cmd/pgo/cmd/update.go index 140c06cecd..f5c5d6eb82 100644 --- a/cmd/pgo/cmd/update.go +++ b/cmd/pgo/cmd/update.go @@ -171,7 +171,6 @@ func init() { UpdateUserCmd.Flags().BoolVar(&PasswordValidAlways, "valid-always", false, "Sets a password to never expire based on expiration time. Takes precedence over --valid-days") UpdateUserCmd.Flags().BoolVar(&RotatePassword, "rotate-password", false, "Rotates the user's password with an automatically generated password. The length of the password is determine by either --password-length or the value set on the server, in that order.") UpdateUserCmd.Flags().StringVarP(&Selector, "selector", "s", "", "The selector to use for cluster filtering.") - } // UpdateCmd represents the update command @@ -191,7 +190,6 @@ var UpdateCmd = &cobra.Command{ pgo update pgorole somerole --pgorole-permission="Cat" pgo update user mycluster --username=testuser --selector=name=mycluster --password=somepassword`, Run: func(cmd *cobra.Command, args []string) { - if len(args) == 0 { fmt.Println(`Error: You must specify the type of resource to update. Valid resource types include: * cluster @@ -214,7 +212,6 @@ var UpdateCmd = &cobra.Command{ * user`) } } - }, } @@ -315,7 +312,6 @@ pgo update user mycluster --username=foobar --disable-login pgo update user mycluster --username=foobar --enable-login `, Run: func(cmd *cobra.Command, args []string) { - if Namespace == "" { Namespace = PGONamespace } @@ -379,7 +375,6 @@ var UpdatePgouserCmd = &cobra.Command{ pgo update pgouser myuser --pgouser-password=somepassword --pgouser-roles=somerole pgo update pgouser myuser --pgouser-password=somepassword --no-prompt`, Run: func(cmd *cobra.Command, args []string) { - if Namespace == "" { Namespace = PGONamespace } @@ -391,13 +386,13 @@ var UpdatePgouserCmd = &cobra.Command{ } }, } + var UpdatePgoroleCmd = &cobra.Command{ Use: "pgorole", Short: "Update a pgorole", Long: `UPDATE allows you to update a pgo role. For example: pgo update pgorole somerole --permissions="Cat,Ls`, Run: func(cmd *cobra.Command, args []string) { - if Namespace == "" { Namespace = PGONamespace } @@ -416,7 +411,6 @@ var UpdateNamespaceCmd = &cobra.Command{ Long: `UPDATE allows you to update a Namespace. For example: pgo update namespace mynamespace`, Run: func(cmd *cobra.Command, args []string) { - if len(args) == 0 { fmt.Println("Error: You must specify the name of a Namespace.") } else { diff --git a/cmd/pgo/cmd/user.go b/cmd/pgo/cmd/user.go index a304ce4870..ae5793167e 100644 --- a/cmd/pgo/cmd/user.go +++ b/cmd/pgo/cmd/user.go @@ -94,7 +94,6 @@ func createUser(args []string, ns string) { } response, err := api.CreateUser(httpclient, &SessionCredentials, &request) - if err != nil { fmt.Println("Error: " + err.Error()) os.Exit(1) @@ -113,7 +112,6 @@ func createUser(args []string, ns string) { // deleteUser ... func deleteUser(args []string, ns string) { - log.Debugf("deleting user %s selector=%s args=%v", Username, Selector, args) if Username == "" { @@ -130,7 +128,6 @@ func deleteUser(args []string, ns string) { } response, err := api.DeleteUser(httpclient, &SessionCredentials, &request) - if err != nil { fmt.Println("Error: " + err.Error()) os.Exit(1) @@ -348,7 +345,6 @@ func showUser(args []string, ns string) { } response, err := api.ShowUser(httpclient, &SessionCredentials, &request) - if err != nil { fmt.Println("Error: " + err.Error()) os.Exit(1) @@ -407,7 +403,6 @@ func updateUser(clusterNames []string, namespace string) { } response, err := api.UpdateUser(httpclient, &SessionCredentials, &request) - if err != nil { fmt.Println("Error: " + err.Error()) os.Exit(1) diff --git a/cmd/pgo/cmd/version.go b/cmd/pgo/cmd/version.go index f66d22e5e2..969f146e85 100644 --- a/cmd/pgo/cmd/version.go +++ b/cmd/pgo/cmd/version.go @@ -47,7 +47,6 @@ func init() { } func showVersion() { - // print the client version fmt.Println("pgo client version " + msgs.PGO_VERSION) @@ -58,7 +57,6 @@ func showVersion() { // otherwise, get the server version response, err := api.ShowVersion(httpclient, &SessionCredentials) - if err != nil { fmt.Println("Error: " + err.Error()) os.Exit(2) diff --git a/cmd/pgo/cmd/watch.go b/cmd/pgo/cmd/watch.go index 69d288a1d8..fce2bcbd74 100644 --- a/cmd/pgo/cmd/watch.go +++ b/cmd/pgo/cmd/watch.go @@ -17,14 +17,15 @@ package cmd import ( "fmt" - "github.com/nsqio/go-nsq" - log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" "math/rand" "os" "os/signal" "syscall" "time" + + "github.com/nsqio/go-nsq" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" ) type TailHandler struct { @@ -45,7 +46,7 @@ var watchCmd = &cobra.Command{ } log.Debug("watch called") - watch(args, Namespace) + watch(args) }, } @@ -57,7 +58,7 @@ func init() { watchCmd.Flags().StringVarP(&PGOEventAddress, "pgo-event-address", "a", "localhost:14150", "The address (host:port) where the event stream is.") } -func watch(args []string, ns string) { +func watch(args []string) { log.Debugf("watch called %v", args) if len(args) == 0 { @@ -66,10 +67,11 @@ func watch(args []string, ns string) { topic := args[0] - var totalMessages = 0 + totalMessages := 0 var channel string rand.Seed(time.Now().UnixNano()) + // #nosec: G404 channel = fmt.Sprintf("tail%06d#ephemeral", rand.Int()%999999) sigChan := make(chan os.Signal, 1) @@ -107,7 +109,6 @@ func watch(args []string, ns string) { for _, consumer := range consumers { <-consumer.StopChan } - } func (th *TailHandler) HandleMessage(m *nsq.Message) error { diff --git a/cmd/pgo/cmd/workflow.go b/cmd/pgo/cmd/workflow.go index 56276cce9e..74b2741f0a 100644 --- a/cmd/pgo/cmd/workflow.go +++ b/cmd/pgo/cmd/workflow.go @@ -34,13 +34,10 @@ func showWorkflow(args []string, ns string) { } printWorkflow(args[0], ns) - } func printWorkflow(id, ns string) { - response, err := api.ShowWorkflow(httpclient, id, &SessionCredentials, ns) - if err != nil { fmt.Println("Error: " + err.Error()) os.Exit(2) @@ -58,5 +55,4 @@ func printWorkflow(id, ns string) { for k, v := range response.Results.Parameters { fmt.Printf("%s%s\n", util.Rpad(k, " ", 20), v) } - } diff --git a/cmd/pgo/generatedocs.go b/cmd/pgo/generatedocs.go index ddd859f214..a5b2d38271 100644 --- a/cmd/pgo/generatedocs.go +++ b/cmd/pgo/generatedocs.go @@ -35,7 +35,6 @@ title: "%s" ` func main() { - fmt.Println("generate CLI markdown") filePrepender := func(filename string) string { diff --git a/cmd/pgo/main.go b/cmd/pgo/main.go index f868a3ac7f..68aa34dee6 100644 --- a/cmd/pgo/main.go +++ b/cmd/pgo/main.go @@ -28,5 +28,4 @@ func main() { fmt.Println(err) os.Exit(1) } - } diff --git a/cmd/pgo/util/validation.go b/cmd/pgo/util/validation.go index 7d90f6a6ac..33690de426 100644 --- a/cmd/pgo/util/validation.go +++ b/cmd/pgo/util/validation.go @@ -31,7 +31,6 @@ var validResourceName = regexp.MustCompile(`^[a-z0-9.\-]+$`).MatchString // https://kubernetes.io/docs/concepts/overview/working-with-objects/names/ // func IsValidForResourceName(target string) bool { - log.Debugf("IsValidForResourceName: %s", target) return validResourceName(target) @@ -48,7 +47,7 @@ func IsValidForResourceName(target string) bool { func ValidateQuantity(quantity, flag string) error { if quantity != "" { if _, err := resource.ParseQuantity(quantity); err != nil { - return fmt.Errorf("Error: \"%s\" - %s", flag, err.Error()) + return fmt.Errorf("Error: \"%s\" - %w", flag, err) } } diff --git a/cmd/postgres-operator/main.go b/cmd/postgres-operator/main.go index aa7e99ab27..f457b79a0d 100644 --- a/cmd/postgres-operator/main.go +++ b/cmd/postgres-operator/main.go @@ -46,7 +46,7 @@ func main() { } debugFlag := os.Getenv("CRUNCHY_DEBUG") - //add logging configuration + // add logging configuration crunchylog.CrunchyLogger(crunchylog.SetParameters()) if debugFlag == "true" { log.SetLevel(log.DebugLevel) @@ -55,7 +55,7 @@ func main() { log.Info("debug flag set to false") } - //give time for pgo-event to start up + // give time for pgo-event to start up time.Sleep(time.Duration(5) * time.Second) newKubernetesClient := func() (*kubeapi.Client, error) { @@ -130,7 +130,6 @@ func main() { // createAndStartNamespaceController creates a namespace controller and then starts it func createAndStartNamespaceController(kubeClientset kubernetes.Interface, controllerManager controller.Manager, stopCh <-chan struct{}) error { - nsKubeInformerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(kubeClientset, time.Duration(*operator.Pgo.Pgo.NamespaceRefreshInterval)*time.Second, kubeinformers.WithTweakListOptions(func(options *metav1.ListOptions) { diff --git a/cmd/postgres-operator/open_telemetry.go b/cmd/postgres-operator/open_telemetry.go index 382079687d..e4b156bb35 100644 --- a/cmd/postgres-operator/open_telemetry.go +++ b/cmd/postgres-operator/open_telemetry.go @@ -62,7 +62,7 @@ func initOpenTelemetry() (func(), error) { options := []stdout.Option{stdout.WithoutMetricExport()} if filename != "" { - file, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + file, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) if err != nil { return nil, fmt.Errorf("unable to open exporter file: %w", err) } diff --git a/internal/apiserver/backrestservice/backrestimpl.go b/internal/apiserver/backrestservice/backrestimpl.go index 7acc51d052..6f1aee538a 100644 --- a/internal/apiserver/backrestservice/backrestimpl.go +++ b/internal/apiserver/backrestservice/backrestimpl.go @@ -349,10 +349,7 @@ func getBackrestRepoPodName(cluster *crv1.Pgcluster) (string, error) { } func isPrimary(pod *v1.Pod, clusterName string) bool { - if pod.ObjectMeta.Labels[config.LABEL_SERVICE_NAME] == clusterName { - return true - } - return false + return pod.ObjectMeta.Labels[config.LABEL_SERVICE_NAME] == clusterName } func isReady(pod *v1.Pod) bool { @@ -364,10 +361,8 @@ func isReady(pod *v1.Pod) bool { readyCount++ } } - if readyCount != containerCount { - return false - } - return true + + return readyCount == containerCount } // isPrimaryReady goes through the pod list to first identify the @@ -385,13 +380,14 @@ func isPrimaryReady(cluster *crv1.Pgcluster, ns string) error { if err != nil { return err } - for _, p := range pods.Items { - if isPrimary(&p, cluster.Spec.Name) && isReady(&p) { + for i := range pods.Items { + p := &pods.Items[i] + if isPrimary(p, cluster.Spec.Name) && isReady(p) { primaryReady = true } } - if primaryReady == false { + if !primaryReady { return errors.New("primary pod is not in Ready state") } return nil @@ -463,7 +459,7 @@ func ShowBackrest(name, selector, ns string) msgs.ShowBackrestResponse { verifyTLS, _ := strconv.ParseBool(operator.GetS3VerifyTLSSetting(c)) // get the pgBackRest info using this legacy function - info, err := getInfo(c.Name, storageType, podname, ns, verifyTLS) + info, err := getInfo(storageType, podname, ns, verifyTLS) // see if the function returned successfully, and if so, unmarshal the JSON if err != nil { log.Error(err) @@ -490,7 +486,7 @@ func ShowBackrest(name, selector, ns string) msgs.ShowBackrestResponse { return response } -func getInfo(clusterName, storageType, podname, ns string, verifyTLS bool) (string, error) { +func getInfo(storageType, podname, ns string, verifyTLS bool) (string, error) { log.Debug("backrest info command requested") cmd := pgBackRestInfoCommand @@ -589,7 +585,7 @@ func Restore(request *msgs.RestoreRequest, ns, pgouser string) msgs.RestoreRespo return resp } - pgtask, err := getRestoreParams(request, ns, *cluster) + pgtask, err := getRestoreParams(request, ns) if err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() @@ -624,7 +620,7 @@ func Restore(request *msgs.RestoreRequest, ns, pgouser string) msgs.RestoreRespo return resp } -func getRestoreParams(request *msgs.RestoreRequest, ns string, cluster crv1.Pgcluster) (*crv1.Pgtask, error) { +func getRestoreParams(request *msgs.RestoreRequest, ns string) (*crv1.Pgtask, error) { var newInstance *crv1.Pgtask spec := crv1.PgtaskSpec{} diff --git a/internal/apiserver/backrestservice/backrestservice.go b/internal/apiserver/backrestservice/backrestservice.go index 49a1edbbdb..c9e5d4b030 100644 --- a/internal/apiserver/backrestservice/backrestservice.go +++ b/internal/apiserver/backrestservice/backrestservice.go @@ -69,12 +69,12 @@ func CreateBackupHandler(w http.ResponseWriter, r *http.Request) { ns, err = apiserver.GetNamespace(apiserver.Clientset, username, request.Namespace) if err != nil { resp.Status = msgs.Status{Code: msgs.Error, Msg: err.Error()} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = CreateBackup(&request, ns, username) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } // DeleteBackrestHandler deletes a targeted backup from a pgBackRest repository @@ -219,19 +219,19 @@ func ShowBackrestHandler(w http.ResponseWriter, r *http.Request) { if clientVersion != msgs.PGO_VERSION { resp.Status = msgs.Status{Code: msgs.Error, Msg: apiserver.VERSION_MISMATCH_ERROR} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } ns, err = apiserver.GetNamespace(apiserver.Clientset, username, namespace) if err != nil { resp.Status = msgs.Status{Code: msgs.Error, Msg: err.Error()} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = ShowBackrest(backupname, selector, ns) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } // RestoreHandler ... @@ -276,10 +276,10 @@ func RestoreHandler(w http.ResponseWriter, r *http.Request) { ns, err = apiserver.GetNamespace(apiserver.Clientset, username, request.Namespace) if err != nil { resp.Status = msgs.Status{Code: msgs.Error, Msg: err.Error()} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = Restore(&request, ns, username) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } diff --git a/internal/apiserver/backupoptions/backupoptionsutil.go b/internal/apiserver/backupoptions/backupoptionsutil.go index b5b7ba314a..5cf5901b29 100644 --- a/internal/apiserver/backupoptions/backupoptionsutil.go +++ b/internal/apiserver/backupoptions/backupoptionsutil.go @@ -35,7 +35,6 @@ type backupOptions interface { // ValidateBackupOpts validates the backup/restore options that can be provided to the various backup // and restore utilities supported by pgo (e.g. pg_dump, pg_restore, pgBackRest, etc.) func ValidateBackupOpts(backupOpts string, request interface{}) error { - // some quick checks to make sure backup opts string is valid and should be processed and validated if strings.TrimSpace(backupOpts) == "" { return nil @@ -52,7 +51,6 @@ func ValidateBackupOpts(backupOpts string, request interface{}) error { return err } else { err := backupOptions.validate(setFlagFieldNames) - if err != nil { return err } @@ -61,7 +59,6 @@ func ValidateBackupOpts(backupOpts string, request interface{}) error { } func convertBackupOptsToStruct(backupOpts string, request interface{}) (backupOptions, []string, error) { - parsedBackupOpts := parseBackupOpts(backupOpts) optsStruct, utilityName, err := createBackupOptionsStruct(backupOpts, request) @@ -92,6 +89,7 @@ func convertBackupOptsToStruct(backupOpts string, request interface{}) (backupOp commandLine.BoolVarP(fieldVal.Addr().Interface().(*bool), flag, flagShort, false, "") case reflect.Slice: commandLine.StringArrayVarP(fieldVal.Addr().Interface().(*[]string), flag, flagShort, nil, "") + default: // no-op } } } @@ -109,7 +107,6 @@ func convertBackupOptsToStruct(backupOpts string, request interface{}) (backupOp } func parseBackupOpts(backupOpts string) []string { - newFields := []string{} var newField string for i, c := range backupOpts { @@ -137,7 +134,6 @@ func parseBackupOpts(backupOpts string) []string { } func createBackupOptionsStruct(backupOpts string, request interface{}) (backupOptions, string, error) { - switch request := request.(type) { case *msgs.CreateBackrestBackupRequest: return &pgBackRestBackupOptions{}, "pgBackRest", nil @@ -215,7 +211,7 @@ func handleCustomParseErrors(err error, usage *bytes.Buffer, optsStruct backupOp func obtainSetFlagFieldNames(commandLine *pflag.FlagSet, structType reflect.Type) []string { var setFlagFieldNames []string - var visitBackupOptFlags = func(flag *pflag.Flag) { + visitBackupOptFlags := func(flag *pflag.Flag) { for i := 0; i < structType.NumField(); i++ { field := structType.Field(i) flagName, _ := field.Tag.Lookup("flag") diff --git a/internal/apiserver/backupoptions/pgbackrestoptions.go b/internal/apiserver/backupoptions/pgbackrestoptions.go index 2c7a1e356e..7926f4c0da 100644 --- a/internal/apiserver/backupoptions/pgbackrestoptions.go +++ b/internal/apiserver/backupoptions/pgbackrestoptions.go @@ -131,7 +131,6 @@ func (backRestBackupOpts pgBackRestBackupOptions) validate(setFlagFieldNames []s var errstrings []string for _, setFlag := range setFlagFieldNames { - switch setFlag { case "BackupType": if !isValidValue([]string{"full", "diff", "incr"}, backRestBackupOpts.BackupType) { @@ -194,11 +193,9 @@ func (backRestBackupOpts pgBackRestBackupOptions) validate(setFlagFieldNames []s } func (backRestRestoreOpts pgBackRestRestoreOptions) validate(setFlagFieldNames []string) error { - var errstrings []string for _, setFlag := range setFlagFieldNames { - switch setFlag { case "TargetAction": if !isValidValue([]string{"pause", "promote", "shutdown"}, backRestRestoreOpts.TargetAction) { diff --git a/internal/apiserver/backupoptions/pgdumpoptions.go b/internal/apiserver/backupoptions/pgdumpoptions.go index 268aa42412..803a417e95 100644 --- a/internal/apiserver/backupoptions/pgdumpoptions.go +++ b/internal/apiserver/backupoptions/pgdumpoptions.go @@ -165,11 +165,9 @@ type pgRestoreOptions struct { } func (dumpOpts pgDumpOptions) validate(setFlagFieldNames []string) error { - var errstrings []string for _, setFlag := range setFlagFieldNames { - switch setFlag { case "Format": if !isValidValue([]string{"p", "plain", "c", "custom", "t", "tar"}, dumpOpts.Format) { @@ -214,11 +212,9 @@ func (dumpOpts pgDumpOptions) validate(setFlagFieldNames []string) error { } func (dumpAllOpts pgDumpAllOptions) validate(setFlagFieldNames []string) error { - var errstrings []string for _, setFlag := range setFlagFieldNames { - switch setFlag { case "SuperUser": if !dumpAllOpts.DisableTriggers { @@ -243,11 +239,9 @@ func (dumpAllOpts pgDumpAllOptions) validate(setFlagFieldNames []string) error { } func (restoreOpts pgRestoreOptions) validate(setFlagFieldNames []string) error { - var errstrings []string for _, setFlag := range setFlagFieldNames { - switch setFlag { case "Format": if !isValidValue([]string{"p", "plain", "c", "custom", "t", "tar"}, restoreOpts.Format) { diff --git a/internal/apiserver/catservice/catimpl.go b/internal/apiserver/catservice/catimpl.go index af86a2a725..e21fd76266 100644 --- a/internal/apiserver/catservice/catimpl.go +++ b/internal/apiserver/catservice/catimpl.go @@ -101,7 +101,6 @@ func Cat(request *msgs.CatRequest, ns string) msgs.CatResponse { // run cat on the postgres pod, remember we are assuming // first container in the pod is always the postgres container. func cat(pod *v1.Pod, ns string, args []string) (string, error) { - command := make([]string, 0) command = append(command, "cat") for i := 1; i < len(args); i++ { @@ -120,10 +119,10 @@ func cat(pod *v1.Pod, ns string, args []string) (string, error) { return stdout, err } -//make sure the parameters to the cat command dont' container mischief +// make sure the parameters to the cat command dont' container mischief func validateArgs(args []string) error { var err error - var bad = "&|;>" + bad := "&|;>" for i := 1; i < len(args); i++ { if strings.ContainsAny(args[i], bad) { diff --git a/internal/apiserver/catservice/catservice.go b/internal/apiserver/catservice/catservice.go index 439274271e..cf25ac5f9a 100644 --- a/internal/apiserver/catservice/catservice.go +++ b/internal/apiserver/catservice/catservice.go @@ -17,10 +17,11 @@ limitations under the License. import ( "encoding/json" + "net/http" + "github.com/crunchydata/postgres-operator/internal/apiserver" msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" - "net/http" ) // CatHandler ... @@ -65,7 +66,7 @@ func CatHandler(w http.ResponseWriter, r *http.Request) { resp := msgs.CatResponse{} resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } @@ -74,9 +75,9 @@ func CatHandler(w http.ResponseWriter, r *http.Request) { resp := msgs.CatResponse{} resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } - json.NewEncoder(w).Encode(catResponse) + _ = json.NewEncoder(w).Encode(catResponse) } diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index d87c01da39..3745d1d220 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -68,7 +68,7 @@ func DeleteCluster(name, selector string, deleteData, deleteBackups bool, ns, pg log.Debugf("delete-data is [%t]", deleteData) log.Debugf("delete-backups is [%t]", deleteBackups) - //get the clusters list + // get the clusters list clusterList, err := apiserver.Clientset.CrunchydataV1().Pgclusters(ns).List(ctx, metav1.ListOptions{LabelSelector: selector}) if err != nil { response.Status.Code = msgs.Error @@ -121,7 +121,6 @@ func DeleteCluster(name, selector string, deleteData, deleteBackups bool, ns, pg } return response - } // ShowCluster ... @@ -143,7 +142,7 @@ func ShowCluster(name, selector, ccpimagetag, ns string, allflag bool) msgs.Show log.Debugf("selector on showCluster is %s", selector) - //get a list of all clusters + // get a list of all clusters clusterList, err := apiserver.Clientset.CrunchydataV1().Pgclusters(ns).List(ctx, metav1.ListOptions{LabelSelector: selector}) if err != nil { response.Status.Code = msgs.Error @@ -153,7 +152,8 @@ func ShowCluster(name, selector, ccpimagetag, ns string, allflag bool) msgs.Show log.Debugf("clusters found len is %d", len(clusterList.Items)) - for _, c := range clusterList.Items { + for i := range clusterList.Items { + c := clusterList.Items[i] detail := msgs.ShowClusterDetail{} detail.Cluster = c detail.Deployments, err = getDeployments(&c, ns) @@ -192,7 +192,6 @@ func ShowCluster(name, selector, ccpimagetag, ns string, allflag bool) msgs.Show } return response - } func getDeployments(cluster *crv1.Pgcluster, ns string) ([]msgs.ShowClusterDeployment, error) { @@ -228,7 +227,7 @@ func GetPods(clientset kubernetes.Interface, cluster *crv1.Pgcluster) ([]msgs.Sh ctx := context.TODO() output := []msgs.ShowClusterPod{} - //get pods, but exclude backup pods and backrest repo + // get pods, but exclude backup pods and backrest repo selector := fmt.Sprintf("%s=%s,%s", config.LABEL_PG_CLUSTER, cluster.GetName(), config.LABEL_PG_DATABASE) log.Debugf("selector for GetPods is %s", selector) @@ -237,14 +236,15 @@ func GetPods(clientset kubernetes.Interface, cluster *crv1.Pgcluster) ([]msgs.Sh return output, err } - for _, p := range pods.Items { + for i := range pods.Items { + p := &pods.Items[i] d := msgs.ShowClusterPod{ PVC: []msgs.ShowClusterPodPVC{}, } d.Name = p.Name d.Phase = string(p.Status.Phase) d.NodeName = p.Spec.NodeName - d.ReadyStatus, d.Ready = getReadyStatus(&p) + d.ReadyStatus, d.Ready = getReadyStatus(p) // get information about several of the PVCs. This borrows from a legacy // method to get this information @@ -262,7 +262,6 @@ func GetPods(clientset kubernetes.Interface, cluster *crv1.Pgcluster) ([]msgs.Sh pvcName := v.VolumeSource.PersistentVolumeClaim.ClaimName // query the PVC to get the storage capacity pvc, err := clientset.CoreV1().PersistentVolumeClaims(cluster.Namespace).Get(ctx, pvcName, metav1.GetOptions{}) - // if there is an error, ignore it, and move on to the next one if err != nil { log.Warn(err) @@ -280,7 +279,7 @@ func GetPods(clientset kubernetes.Interface, cluster *crv1.Pgcluster) ([]msgs.Sh } d.Primary = false - d.Type = getType(&p, cluster.Spec.Name) + d.Type = getType(p) if d.Type == msgs.PodTypePrimary { d.Primary = true } @@ -289,7 +288,6 @@ func GetPods(clientset kubernetes.Interface, cluster *crv1.Pgcluster) ([]msgs.Sh } return output, err - } func getServices(cluster *crv1.Pgcluster, ns string) ([]msgs.ShowClusterService, error) { @@ -368,7 +366,6 @@ func TestCluster(name, selector, ns, pgouser string, allFlag bool) msgs.ClusterT // Find a list of a clusters that match the given selector clusterList, err := apiserver.Clientset.CrunchydataV1().Pgclusters(ns).List(ctx, metav1.ListOptions{LabelSelector: selector}) - // If the response errors, return here, as we won't be able to return any // useful information in the test if err != nil { @@ -381,7 +378,8 @@ func TestCluster(name, selector, ns, pgouser string, allFlag bool) msgs.ClusterT log.Debugf("Total clusters found: %d", len(clusterList.Items)) // Iterate through each cluster and perform the various tests against them - for _, c := range clusterList.Items { + for i := range clusterList.Items { + c := clusterList.Items[i] // Set up the object that will be appended to the response that // indicates the availability of the endpoints / instances for this // cluster @@ -397,7 +395,6 @@ func TestCluster(name, selector, ns, pgouser string, allFlag bool) msgs.ClusterT // Get the PostgreSQL instances! log.Debugf("Looking up instance pods for cluster: %s", c.Name) pods, err := GetPrimaryAndReplicaPods(&c, ns) - // if there is an error with returning the primary/replica instances, // then error and continue if err != nil { @@ -718,7 +715,7 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. resp.Status.Msg = err.Error() return resp } - //add a label for the custom config + // add a label for the custom config userLabelsMap[config.LABEL_CUSTOM_CONFIG] = request.CustomConfig } @@ -815,7 +812,7 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. } if request.ReplicaStorageConfig != "" { - if apiserver.IsValidStorageName(request.ReplicaStorageConfig) == false { + if !apiserver.IsValidStorageName(request.ReplicaStorageConfig) { resp.Status.Code = msgs.Error resp.Status.Msg = request.ReplicaStorageConfig + " Storage config was not found " return resp @@ -897,7 +894,7 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. } } - validateConfigPolicies(clusterName, request.Policies, ns) + _ = validateConfigPolicies(clusterName, request.Policies, ns) // create the user secrets // first, the superuser @@ -976,7 +973,6 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. backrestSecret, err := apiserver.Clientset. CoreV1().Secrets(request.Namespace). Get(ctx, request.BackrestS3CASecretName, metav1.GetOptions{}) - if err != nil { log.Error(err) resp.Status.Code = msgs.Error @@ -1019,7 +1015,7 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. return resp } - //create a workflow for this new cluster + // create a workflow for this new cluster id, err = createWorkflowTask(clusterName, ns, pgouser) if err != nil { log.Error(err) @@ -1034,7 +1030,7 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. newInstance.Spec.UserLabels[config.LABEL_WORKFLOW_ID] = id resp.Result.Database = newInstance.Spec.Database - //create CRD for new cluster + // create CRD for new cluster _, err = apiserver.Clientset.CrunchydataV1().Pgclusters(ns).Create(ctx, newInstance, metav1.CreateOptions{}) if err != nil { resp.Status.Code = msgs.Error @@ -1075,7 +1071,7 @@ func validateConfigPolicies(clusterName, PoliciesFlag, ns string) error { log.Error("error getting pgpolicy " + v + err.Error()) return err } - //create a pgtask to add the policy after the db is ready + // create a pgtask to add the policy after the db is ready } spec := crv1.PgtaskSpec{} @@ -1105,7 +1101,6 @@ func validateConfigPolicies(clusterName, PoliciesFlag, ns string) error { } func getClusterParams(request *msgs.CreateClusterRequest, name string, userLabelsMap map[string]string, ns string) *crv1.Pgcluster { - spec := crv1.PgclusterSpec{ Annotations: crv1.ClusterAnnotations{ Backrest: map[string]string{}, @@ -1393,7 +1388,7 @@ func getClusterParams(request *msgs.CreateClusterRequest, name string, userLabel spec.UserLabels = userLabelsMap spec.UserLabels[config.LABEL_PGO_VERSION] = msgs.PGO_VERSION - //override any values from config file + // override any values from config file str = apiserver.Pgo.Cluster.Port log.Debugf("%s", apiserver.Pgo.Cluster.Port) if str != "" { @@ -1586,13 +1581,12 @@ func getReadyStatus(pod *v1.Pod) (string, bool) { equal = true } return fmt.Sprintf("%d/%d", readyCount, containerCount), equal - } func createWorkflowTask(clusterName, ns, pgouser string) (string, error) { ctx := context.TODO() - //create pgtask CRD + // create pgtask CRD spec := crv1.PgtaskSpec{} spec.Namespace = ns spec.Name = clusterName + "-" + crv1.PgtaskWorkflowCreateClusterType @@ -1628,9 +1622,7 @@ func createWorkflowTask(clusterName, ns, pgouser string) (string, error) { return spec.Parameters[crv1.PgtaskWorkflowID], err } -func getType(pod *v1.Pod, clusterName string) string { - - //log.Debugf("%v\n", pod.ObjectMeta.Labels) +func getType(pod *v1.Pod) string { if pod.ObjectMeta.Labels[config.LABEL_PGO_BACKREST_REPO] != "" { return msgs.PodTypePgbackrest } else if pod.ObjectMeta.Labels[config.LABEL_PGBOUNCER] != "" { @@ -1641,7 +1633,6 @@ func getType(pod *v1.Pod, clusterName string) string { return msgs.PodTypeReplica } return msgs.PodTypeUnknown - } func validateCustomConfig(configmapname, ns string) (bool, error) { @@ -1727,7 +1718,6 @@ func createUserSecret(request *msgs.CreateClusterRequest, cluster *crv1.Pgcluste // now attempt to load said secret oldPassword, err := util.GetPasswordFromSecret(apiserver.Clientset, cluster.Spec.Namespace, secretFromSecretName) - // if there is an error, abandon here, otherwise set the oldPassword as the // current password if err != nil { @@ -1745,7 +1735,6 @@ func createUserSecret(request *msgs.CreateClusterRequest, cluster *crv1.Pgcluste } generatedPassword, err := util.GeneratePassword(passwordLength) - // if the password fails to generate, return the error if err != nil { return "", "", err @@ -1855,7 +1844,7 @@ func UpdateCluster(request *msgs.UpdateClusterRequest) msgs.UpdateClusterRespons clusterList := crv1.PgclusterList{} - //get the clusters list + // get the clusters list if request.AllFlag { cl, err := apiserver.Clientset.CrunchydataV1().Pgclusters(request.Namespace).List(ctx, metav1.ListOptions{}) if err != nil { @@ -1892,15 +1881,17 @@ func UpdateCluster(request *msgs.UpdateClusterRequest) msgs.UpdateClusterRespons return response } - for i, cluster := range clusterList.Items { + for i := range clusterList.Items { + cluster := clusterList.Items[i] - //set autofail=true or false on each pgcluster CRD + // set autofail=true or false on each pgcluster CRD // Make the change based on the value of Autofail vis-a-vis UpdateClusterAutofailStatus switch request.Autofail { case msgs.UpdateClusterAutofailEnable: cluster.ObjectMeta.Labels[config.LABEL_AUTOFAIL] = "true" case msgs.UpdateClusterAutofailDisable: cluster.ObjectMeta.Labels[config.LABEL_AUTOFAIL] = "false" + case msgs.UpdateClusterAutofailDoNothing: // no-op } // enable or disable the metrics collection sidecar @@ -1925,6 +1916,7 @@ func UpdateCluster(request *msgs.UpdateClusterRequest) msgs.UpdateClusterRespons } case msgs.UpdateClusterStandbyDisable: cluster.Spec.Standby = false + case msgs.UpdateClusterStandbyDoNothing: // no-op } // return an error if attempting to enable standby for a cluster that does not have the // required S3 settings @@ -2032,7 +2024,7 @@ func UpdateCluster(request *msgs.UpdateClusterRequest) msgs.UpdateClusterRespons // if it fails...just put a in the logs. if cluster.Spec.Exporter && request.ExporterRotatePassword { if err := clusteroperator.RotateExporterPassword(apiserver.Clientset, apiserver.RESTConfig, - &clusterList.Items[i]); err != nil { + &cluster); err != nil { log.Error(err) } } @@ -2094,15 +2086,16 @@ func GetPrimaryAndReplicaPods(cluster *crv1.Pgcluster, ns string) ([]msgs.ShowCl if err != nil { return output, err } - for _, p := range pods.Items { + for i := range pods.Items { + p := &pods.Items[i] d := msgs.ShowClusterPod{} d.Name = p.Name d.Phase = string(p.Status.Phase) d.NodeName = p.Spec.NodeName - d.ReadyStatus, d.Ready = getReadyStatus(&p) + d.ReadyStatus, d.Ready = getReadyStatus(p) d.Primary = false - d.Type = getType(&p, cluster.Spec.Name) + d.Type = getType(p) if d.Type == msgs.PodTypePrimary { d.Primary = true } @@ -2119,15 +2112,16 @@ func GetPrimaryAndReplicaPods(cluster *crv1.Pgcluster, ns string) ([]msgs.ShowCl if err != nil { return output, err } - for _, p := range pods.Items { + for i := range pods.Items { + p := &pods.Items[i] d := msgs.ShowClusterPod{} d.Name = p.Name d.Phase = string(p.Status.Phase) d.NodeName = p.Spec.NodeName - d.ReadyStatus, d.Ready = getReadyStatus(&p) + d.ReadyStatus, d.Ready = getReadyStatus(p) d.Primary = false - d.Type = getType(&p, cluster.Spec.Name) + d.Type = getType(p) if d.Type == msgs.PodTypePrimary { d.Primary = true } @@ -2136,7 +2130,6 @@ func GetPrimaryAndReplicaPods(cluster *crv1.Pgcluster, ns string) ([]msgs.ShowCl } return output, err - } // setClusterAnnotationGroup helps with setting the specific annotation group @@ -2155,7 +2148,6 @@ func setClusterAnnotationGroup(annotationGroup, annotations map[string]string) { // a new cluster. This includes ensuring the type provided is valid, and that the required // configuration settings (s3 bucket, region, etc.) are also present func validateBackrestStorageTypeOnCreate(request *msgs.CreateClusterRequest) error { - requestBackRestStorageType := request.BackrestStorageType if requestBackRestStorageType != "" && !util.IsValidBackrestStorageType(requestBackRestStorageType) { diff --git a/internal/apiserver/clusterservice/clusterservice.go b/internal/apiserver/clusterservice/clusterservice.go index d0f31df636..32b904eb61 100644 --- a/internal/apiserver/clusterservice/clusterservice.go +++ b/internal/apiserver/clusterservice/clusterservice.go @@ -77,19 +77,18 @@ func CreateClusterHandler(w http.ResponseWriter, r *http.Request) { if request.ClientVersion != msgs.PGO_VERSION { resp.Status.Code = msgs.Error resp.Status.Msg = apiserver.VERSION_MISMATCH_ERROR - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } ns, err = apiserver.GetNamespace(apiserver.Clientset, username, request.Namespace) if err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = CreateCluster(&request, ns, username) - json.NewEncoder(w).Encode(resp) - + _ = json.NewEncoder(w).Encode(resp) } // ShowClusterHandler ... @@ -150,7 +149,7 @@ func ShowClusterHandler(w http.ResponseWriter, r *http.Request) { if clientVersion != msgs.PGO_VERSION { resp.Status = msgs.Status{Code: msgs.Error, Msg: apiserver.VERSION_MISMATCH_ERROR} resp.Results = make([]msgs.ShowClusterDetail, 0) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } @@ -158,13 +157,12 @@ func ShowClusterHandler(w http.ResponseWriter, r *http.Request) { if err != nil { resp.Status = msgs.Status{Code: msgs.Error, Msg: err.Error()} resp.Results = make([]msgs.ShowClusterDetail, 0) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = ShowCluster(clustername, selector, ccpimagetag, ns, allflag) - json.NewEncoder(w).Encode(resp) - + _ = json.NewEncoder(w).Encode(resp) } // DeleteClusterHandler ... @@ -225,19 +223,18 @@ func DeleteClusterHandler(w http.ResponseWriter, r *http.Request) { if clientVersion != msgs.PGO_VERSION { resp.Status = msgs.Status{Code: msgs.Error, Msg: apiserver.VERSION_MISMATCH_ERROR} resp.Results = make([]string, 0) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } ns, err = apiserver.GetNamespace(apiserver.Clientset, username, namespace) if err != nil { resp.Status = msgs.Status{Code: msgs.Error, Msg: err.Error()} resp.Results = make([]string, 0) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = DeleteCluster(clustername, selector, deleteData, deleteBackups, ns, username) - json.NewEncoder(w).Encode(resp) - + _ = json.NewEncoder(w).Encode(resp) } // TestClusterHandler ... @@ -290,19 +287,19 @@ func TestClusterHandler(w http.ResponseWriter, r *http.Request) { if clientVersion != msgs.PGO_VERSION { resp.Status = msgs.Status{Code: msgs.Error, Msg: apiserver.VERSION_MISMATCH_ERROR} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } ns, err = apiserver.GetNamespace(apiserver.Clientset, username, namespace) if err != nil { resp.Status = msgs.Status{Code: msgs.Error, Msg: err.Error()} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = TestCluster(clustername, selector, ns, username, request.AllFlag) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } // UpdateClusterHandler ... @@ -352,7 +349,7 @@ func UpdateClusterHandler(w http.ResponseWriter, r *http.Request) { if clientVersion != msgs.PGO_VERSION { resp.Status = msgs.Status{Code: msgs.Error, Msg: apiserver.VERSION_MISMATCH_ERROR} resp.Results = make([]string, 0) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } @@ -360,11 +357,10 @@ func UpdateClusterHandler(w http.ResponseWriter, r *http.Request) { if err != nil { resp.Status = msgs.Status{Code: msgs.Error, Msg: err.Error()} resp.Results = make([]string, 0) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = UpdateCluster(&request) - json.NewEncoder(w).Encode(resp) - + _ = json.NewEncoder(w).Encode(resp) } diff --git a/internal/apiserver/clusterservice/scaleimpl.go b/internal/apiserver/clusterservice/scaleimpl.go index 4148283bfb..00be04242e 100644 --- a/internal/apiserver/clusterservice/scaleimpl.go +++ b/internal/apiserver/clusterservice/scaleimpl.go @@ -73,10 +73,10 @@ func ScaleCluster(name, replicaCount, storageConfig, nodeLabel, spec := crv1.PgreplicaSpec{} - //refer to the cluster's replica storage setting by default + // refer to the cluster's replica storage setting by default spec.ReplicaStorage = cluster.Spec.ReplicaStorage - //allow for user override + // allow for user override if storageConfig != "" { spec.ReplicaStorage, _ = apiserver.Pgo.GetStorageSpec(storageConfig) } @@ -97,7 +97,7 @@ func ScaleCluster(name, replicaCount, storageConfig, nodeLabel, spec.UserLabels[config.LABEL_SERVICE_TYPE] = serviceType } - //set replica node lables to blank to start with, then check for overrides + // set replica node lables to blank to start with, then check for overrides spec.UserLabels[config.LABEL_NODE_LABEL_KEY] = "" spec.UserLabels[config.LABEL_NODE_LABEL_VALUE] = "" @@ -204,7 +204,6 @@ func ScaleQuery(name, ns string) msgs.ScaleQueryResponse { } replicationStatusResponse, err := util.ReplicationStatus(replicationStatusRequest, false, true) - // if an error is return, log the message, and return the response if err != nil { log.Error(err.Error()) @@ -301,7 +300,7 @@ func ScaleDown(deleteData bool, clusterName, replicaName, ns string) msgs.ScaleD return response } - //create the rmdata task which does the cleanup + // create the rmdata task which does the cleanup clusterPGHAScope := cluster.ObjectMeta.Labels[config.LABEL_PGHA_SCOPE] deleteBackups := false diff --git a/internal/apiserver/clusterservice/scaleservice.go b/internal/apiserver/clusterservice/scaleservice.go index 92db853216..d0d54d6119 100644 --- a/internal/apiserver/clusterservice/scaleservice.go +++ b/internal/apiserver/clusterservice/scaleservice.go @@ -117,14 +117,14 @@ func ScaleClusterHandler(w http.ResponseWriter, r *http.Request) { if clientVersion != msgs.PGO_VERSION { resp.Status = msgs.Status{Code: msgs.Error, Msg: apiserver.VERSION_MISMATCH_ERROR} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } ns, err = apiserver.GetNamespace(apiserver.Clientset, username, namespace) if err != nil { resp.Status = msgs.Status{Code: msgs.Error, Msg: err.Error()} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } @@ -132,7 +132,7 @@ func ScaleClusterHandler(w http.ResponseWriter, r *http.Request) { resp = ScaleCluster(clusterName, replicaCount, storageConfig, nodeLabel, ccpImageTag, serviceType, ns, username) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } // ScaleQueryHandler ... @@ -190,19 +190,19 @@ func ScaleQueryHandler(w http.ResponseWriter, r *http.Request) { if clientVersion != msgs.PGO_VERSION { resp.Status = msgs.Status{Code: msgs.Error, Msg: apiserver.VERSION_MISMATCH_ERROR} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } ns, err = apiserver.GetNamespace(apiserver.Clientset, username, namespace) if err != nil { resp.Status = msgs.Status{Code: msgs.Error, Msg: err.Error()} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = ScaleQuery(clusterName, ns) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } // ScaleDownHandler ... @@ -273,23 +273,23 @@ func ScaleDownHandler(w http.ResponseWriter, r *http.Request) { deleteData, err := strconv.ParseBool(tmp) if err != nil { resp.Status = msgs.Status{Code: msgs.Error, Msg: err.Error()} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } if clientVersion != msgs.PGO_VERSION { resp.Status = msgs.Status{Code: msgs.Error, Msg: apiserver.VERSION_MISMATCH_ERROR} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } ns, err = apiserver.GetNamespace(apiserver.Clientset, username, namespace) if err != nil { resp.Status = msgs.Status{Code: msgs.Error, Msg: err.Error()} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = ScaleDown(deleteData, clusterName, replicaName, ns) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } diff --git a/internal/apiserver/common.go b/internal/apiserver/common.go index 15c070e0dc..a4c392a817 100644 --- a/internal/apiserver/common.go +++ b/internal/apiserver/common.go @@ -59,7 +59,7 @@ func CreateRMDataTask(clusterName, replicaName, taskName string, deleteBackups, ctx := context.TODO() var err error - //create pgtask CRD + // create pgtask CRD spec := crv1.PgtaskSpec{} spec.Namespace = ns spec.Name = taskName @@ -91,7 +91,6 @@ func CreateRMDataTask(clusterName, replicaName, taskName string, deleteBackups, } return err - } func GetBackrestStorageTypes() []string { diff --git a/internal/apiserver/configservice/configservice.go b/internal/apiserver/configservice/configservice.go index 1f70934888..d4066b5017 100644 --- a/internal/apiserver/configservice/configservice.go +++ b/internal/apiserver/configservice/configservice.go @@ -17,10 +17,11 @@ limitations under the License. import ( "encoding/json" + "net/http" + "github.com/crunchydata/postgres-operator/internal/apiserver" msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" - "net/http" ) // ShowConfigHandler ... @@ -68,17 +69,17 @@ func ShowConfigHandler(w http.ResponseWriter, r *http.Request) { if clientVersion != msgs.PGO_VERSION { resp.Status = msgs.Status{Code: msgs.Error, Msg: apiserver.VERSION_MISMATCH_ERROR} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } _, err = apiserver.GetNamespace(apiserver.Clientset, username, namespace) if err != nil { resp.Status = msgs.Status{Code: msgs.Error, Msg: err.Error()} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = ShowConfig() - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } diff --git a/internal/apiserver/dfservice/dfimpl.go b/internal/apiserver/dfservice/dfimpl.go index 5a0186f41b..1a24d235d2 100644 --- a/internal/apiserver/dfservice/dfimpl.go +++ b/internal/apiserver/dfservice/dfimpl.go @@ -126,7 +126,6 @@ func getClaimCapacity(clientset kubernetes.Interface, pvcName, ns string) (strin log.Debugf("in df pvc name found to be %s", pvcName) pvc, err := clientset.CoreV1().PersistentVolumeClaims(ns).Get(ctx, pvcName, metav1.GetOptions{}) - if err != nil { log.Error(err) return "", err @@ -158,7 +157,6 @@ func getClusterDf(cluster *crv1.Pgcluster, clusterResultsChannel chan msgs.DfDet } pods, err := apiserver.Clientset.CoreV1().Pods(cluster.Spec.Namespace).List(ctx, options) - // if there is an error attempting to get the pods, just return if err != nil { errorChannel <- err @@ -307,7 +305,6 @@ func getPodDf(cluster *crv1.Pgcluster, pod *v1.Pod, podResultsChannel chan msgs. stdout, stderr, err := kubeapi.ExecToPodThroughAPI(apiserver.RESTConfig, apiserver.Clientset, cmd, pvcContainerName, pod.Name, cluster.Spec.Namespace, nil) - // if the command fails, exit here if err != nil { err := fmt.Errorf(stderr) @@ -318,7 +315,7 @@ func getPodDf(cluster *crv1.Pgcluster, pod *v1.Pod, podResultsChannel chan msgs. // have to parse the size out from the statement. Size is in bytes if _, err = fmt.Sscan(stdout, &result.PVCUsed); err != nil { - err := fmt.Errorf("could not find the size of pvc %s: %v", result.PVCName, err) + err := fmt.Errorf("could not find the size of pvc %s: %w", result.PVCName, err) log.Error(err) errorChannel <- err return diff --git a/internal/apiserver/dfservice/dfservice.go b/internal/apiserver/dfservice/dfservice.go index 325e20257a..89c8796b27 100644 --- a/internal/apiserver/dfservice/dfservice.go +++ b/internal/apiserver/dfservice/dfservice.go @@ -69,7 +69,7 @@ func DfHandler(w http.ResponseWriter, r *http.Request) { if err := json.NewDecoder(r.Body).Decode(&request); err != nil { response := CreateErrorResponse(err.Error()) - json.NewEncoder(w).Encode(response) + _ = json.NewEncoder(w).Encode(response) return } @@ -84,14 +84,14 @@ func DfHandler(w http.ResponseWriter, r *http.Request) { // check that the client versions match. If they don't, error out if request.ClientVersion != msgs.PGO_VERSION { response := CreateErrorResponse(apiserver.VERSION_MISMATCH_ERROR) - json.NewEncoder(w).Encode(response) + _ = json.NewEncoder(w).Encode(response) return } // ensure that the user has access to this namespace. if not, error out if _, err := apiserver.GetNamespace(apiserver.Clientset, username, request.Namespace); err != nil { response := CreateErrorResponse(err.Error()) - json.NewEncoder(w).Encode(response) + _ = json.NewEncoder(w).Encode(response) return } @@ -99,5 +99,5 @@ func DfHandler(w http.ResponseWriter, r *http.Request) { response := DfCluster(request) // turn the response into JSON - json.NewEncoder(w).Encode(response) + _ = json.NewEncoder(w).Encode(response) } diff --git a/internal/apiserver/failoverservice/failoverimpl.go b/internal/apiserver/failoverservice/failoverimpl.go index 0e6e56df58..7886e3ddfa 100644 --- a/internal/apiserver/failoverservice/failoverimpl.go +++ b/internal/apiserver/failoverservice/failoverimpl.go @@ -74,7 +74,7 @@ func CreateFailover(request *msgs.CreateFailoverRequest, ns, pgouser string) msg spec.Name = request.ClusterName + "-" + config.LABEL_FAILOVER // previous failovers will leave a pgtask so remove it first - apiserver.Clientset.CrunchydataV1().Pgtasks(ns).Delete(ctx, spec.Name, metav1.DeleteOptions{}) + _ = apiserver.Clientset.CrunchydataV1().Pgtasks(ns).Delete(ctx, spec.Name, metav1.DeleteOptions{}) spec.TaskType = crv1.PgtaskFailover spec.Parameters = make(map[string]string) @@ -109,7 +109,6 @@ func CreateFailover(request *msgs.CreateFailoverRequest, ns, pgouser string) msg // over to // pgo failover mycluster --query func QueryFailover(name, ns string) msgs.QueryFailoverResponse { - response := msgs.QueryFailoverResponse{ Results: make([]msgs.FailoverTargetSpec, 0), Status: msgs.Status{Code: msgs.Ok, Msg: ""}, @@ -139,7 +138,6 @@ func QueryFailover(name, ns string) msgs.QueryFailoverResponse { } replicationStatusResponse, err := util.ReplicationStatus(replicationStatusRequest, false, false) - // if an error is return, log the message, and return the response if err != nil { log.Error(err.Error()) @@ -175,7 +173,6 @@ func QueryFailover(name, ns string) msgs.QueryFailoverResponse { func validateClusterName(clusterName, ns string) (*crv1.Pgcluster, error) { ctx := context.TODO() cluster, err := apiserver.Clientset.CrunchydataV1().Pgclusters(ns).Get(ctx, clusterName, metav1.GetOptions{}) - if err != nil { return cluster, errors.New("no cluster found named " + clusterName) } @@ -219,5 +216,4 @@ func isValidFailoverTarget(deployName, clusterName, ns string) (*v1.Deployment, } return &deployments.Items[0], nil - } diff --git a/internal/apiserver/failoverservice/failoverservice.go b/internal/apiserver/failoverservice/failoverservice.go index 164d7b1545..ad42873f77 100644 --- a/internal/apiserver/failoverservice/failoverservice.go +++ b/internal/apiserver/failoverservice/failoverservice.go @@ -17,11 +17,12 @@ limitations under the License. import ( "encoding/json" + "net/http" + "github.com/crunchydata/postgres-operator/internal/apiserver" msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" - "net/http" ) // CreateFailoverHandler ... @@ -65,20 +66,20 @@ func CreateFailoverHandler(w http.ResponseWriter, r *http.Request) { if request.ClientVersion != msgs.PGO_VERSION { resp.Status = msgs.Status{Code: msgs.Error, Msg: apiserver.VERSION_MISMATCH_ERROR} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } ns, err = apiserver.GetNamespace(apiserver.Clientset, username, request.Namespace) if err != nil { resp.Status = msgs.Status{Code: msgs.Error, Msg: err.Error()} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = CreateFailover(&request, ns, username) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } // QueryFailoverHandler ... @@ -137,17 +138,17 @@ func QueryFailoverHandler(w http.ResponseWriter, r *http.Request) { if clientVersion != msgs.PGO_VERSION { resp.Status = msgs.Status{Code: msgs.Error, Msg: apiserver.VERSION_MISMATCH_ERROR} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } ns, err = apiserver.GetNamespace(apiserver.Clientset, username, namespace) if err != nil { resp.Status = msgs.Status{Code: msgs.Error, Msg: err.Error()} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = QueryFailover(name, ns) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } diff --git a/internal/apiserver/labelservice/labelimpl.go b/internal/apiserver/labelservice/labelimpl.go index a7129249dd..ed12991f58 100644 --- a/internal/apiserver/labelservice/labelimpl.go +++ b/internal/apiserver/labelservice/labelimpl.go @@ -50,7 +50,7 @@ func Label(request *msgs.LabelRequest, ns, pgouser string) msgs.LabelResponse { return resp } - labelsMap, err = validateLabel(request.LabelCmdLabel, ns) + labelsMap, err = validateLabel(request.LabelCmdLabel) if err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = "labels not formatted correctly" @@ -90,7 +90,7 @@ func Label(request *msgs.LabelRequest, ns, pgouser string) msgs.LabelResponse { } clusterList = *cl } else { - //each arg represents a cluster name + // each arg represents a cluster name items := make([]crv1.Pgcluster, 0) for _, cluster := range request.Args { result, err := apiserver.Clientset.CrunchydataV1().Pgclusters(ns).Get(ctx, cluster, metav1.GetOptions{}) @@ -112,7 +112,6 @@ func Label(request *msgs.LabelRequest, ns, pgouser string) msgs.LabelResponse { addLabels(clusterList.Items, request.DryRun, request.LabelCmdLabel, labelsMap, ns, pgouser) return resp - } func addLabels(items []crv1.Pgcluster, DryRun bool, LabelCmdLabel string, newLabels map[string]string, ns, pgouser string) { @@ -134,7 +133,7 @@ func addLabels(items []crv1.Pgcluster, DryRun bool, LabelCmdLabel string, newLab log.Error(err.Error()) } - //publish event for create label + // publish event for create label topics := make([]string, 1) topics[0] = events.EventTopicCluster @@ -158,7 +157,7 @@ func addLabels(items []crv1.Pgcluster, DryRun bool, LabelCmdLabel string, newLab } for i := 0; i < len(items); i++ { - //get deployments for this CRD + // get deployments for this CRD selector := config.LABEL_PG_CLUSTER + "=" + items[i].Spec.Name deployments, err := apiserver.Clientset. AppsV1().Deployments(ns). @@ -168,7 +167,7 @@ func addLabels(items []crv1.Pgcluster, DryRun bool, LabelCmdLabel string, newLab } for _, d := range deployments.Items { - //update Deployment with the label + // update Deployment with the label if !DryRun { log.Debugf("patching deployment %s: %s", d.Name, patchBytes) _, err := apiserver.Clientset.AppsV1().Deployments(ns). @@ -182,7 +181,7 @@ func addLabels(items []crv1.Pgcluster, DryRun bool, LabelCmdLabel string, newLab } } -func validateLabel(LabelCmdLabel, ns string) (map[string]string, error) { +func validateLabel(LabelCmdLabel string) (map[string]string, error) { var err error labelMap := make(map[string]string) userValues := strings.Split(LabelCmdLabel, ",") @@ -225,7 +224,7 @@ func DeleteLabel(request *msgs.DeleteLabelRequest, ns string) msgs.LabelResponse return resp } - labelsMap, err = validateLabel(request.LabelCmdLabel, ns) + labelsMap, err = validateLabel(request.LabelCmdLabel) if err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = "labels not formatted correctly" @@ -263,7 +262,7 @@ func DeleteLabel(request *msgs.DeleteLabelRequest, ns string) msgs.LabelResponse } clusterList = *cl } else { - //each arg represents a cluster name + // each arg represents a cluster name items := make([]crv1.Pgcluster, 0) for _, cluster := range request.Args { result, err := apiserver.Clientset.CrunchydataV1().Pgclusters(ns).Get(ctx, cluster, metav1.GetOptions{}) @@ -282,7 +281,7 @@ func DeleteLabel(request *msgs.DeleteLabelRequest, ns string) msgs.LabelResponse resp.Results = append(resp.Results, "deleting label from "+c.Spec.Name) } - err = deleteLabels(clusterList.Items, request.LabelCmdLabel, labelsMap, ns) + err = deleteLabels(clusterList.Items, labelsMap, ns) if err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() @@ -290,10 +289,9 @@ func DeleteLabel(request *msgs.DeleteLabelRequest, ns string) msgs.LabelResponse } return resp - } -func deleteLabels(items []crv1.Pgcluster, LabelCmdLabel string, labelsMap map[string]string, ns string) error { +func deleteLabels(items []crv1.Pgcluster, labelsMap map[string]string, ns string) error { ctx := context.TODO() patch := kubeapi.NewMergePatch() for key := range labelsMap { @@ -316,7 +314,7 @@ func deleteLabels(items []crv1.Pgcluster, LabelCmdLabel string, labelsMap map[st } for i := 0; i < len(items); i++ { - //get deployments for this CRD + // get deployments for this CRD selector := config.LABEL_PG_CLUSTER + "=" + items[i].Spec.Name deployments, err := apiserver.Clientset. AppsV1().Deployments(ns). diff --git a/internal/apiserver/labelservice/labelservice.go b/internal/apiserver/labelservice/labelservice.go index f13054fd17..e166d2d03f 100644 --- a/internal/apiserver/labelservice/labelservice.go +++ b/internal/apiserver/labelservice/labelservice.go @@ -17,10 +17,11 @@ limitations under the License. import ( "encoding/json" + "net/http" + "github.com/crunchydata/postgres-operator/internal/apiserver" msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" - "net/http" ) // LabelHandler ... @@ -64,20 +65,20 @@ func LabelHandler(w http.ResponseWriter, r *http.Request) { if request.ClientVersion != msgs.PGO_VERSION { resp.Status.Msg = apiserver.VERSION_MISMATCH_ERROR resp.Status.Code = msgs.Error - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } ns, err = apiserver.GetNamespace(apiserver.Clientset, username, request.Namespace) if err != nil { resp.Status = msgs.Status{Msg: err.Error(), Code: msgs.Error} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = Label(&request, ns, username) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } // DeleteLabelHandler ... @@ -120,18 +121,18 @@ func DeleteLabelHandler(w http.ResponseWriter, r *http.Request) { if request.ClientVersion != msgs.PGO_VERSION { resp.Status = msgs.Status{Msg: apiserver.VERSION_MISMATCH_ERROR, Code: msgs.Error} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } ns, err = apiserver.GetNamespace(apiserver.Clientset, username, request.Namespace) if err != nil { resp.Status = msgs.Status{Msg: err.Error(), Code: msgs.Error} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = DeleteLabel(&request, ns) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } diff --git a/internal/apiserver/namespaceservice/namespaceimpl.go b/internal/apiserver/namespaceservice/namespaceimpl.go index 36af3b14e2..24b6234b48 100644 --- a/internal/apiserver/namespaceservice/namespaceimpl.go +++ b/internal/apiserver/namespaceservice/namespaceimpl.go @@ -36,7 +36,7 @@ func ShowNamespace(clientset kubernetes.Interface, username string, request *msg resp.Username = username resp.Results = make([]msgs.NamespaceResult, 0) - //namespaceList := util.GetNamespaces() + // namespaceList := util.GetNamespaces() nsList := make([]string, 0) @@ -91,14 +91,13 @@ func ShowNamespace(clientset kubernetes.Interface, username string, request *msg // CreateNamespace ... func CreateNamespace(clientset kubernetes.Interface, createdBy string, request *msgs.CreateNamespaceRequest) msgs.CreateNamespaceResponse { - log.Debugf("CreateNamespace %v", request) resp := msgs.CreateNamespaceResponse{} resp.Status.Code = msgs.Ok resp.Status.Msg = "" resp.Results = make([]string, 0) - //iterate thru all the args (namespace names) + // iterate thru all the args (namespace names) for _, namespace := range request.Args { if err := ns.CreateNamespace(clientset, apiserver.InstallationName, @@ -112,7 +111,6 @@ func CreateNamespace(clientset kubernetes.Interface, createdBy string, request * } return resp - } // DeleteNamespace ... @@ -125,7 +123,6 @@ func DeleteNamespace(clientset kubernetes.Interface, deletedBy string, request * for _, namespace := range request.Args { err := ns.DeleteNamespace(clientset, apiserver.InstallationName, apiserver.PgoNamespace, deletedBy, namespace) - if err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() @@ -136,19 +133,17 @@ func DeleteNamespace(clientset kubernetes.Interface, deletedBy string, request * } return resp - } // UpdateNamespace ... func UpdateNamespace(clientset kubernetes.Interface, updatedBy string, request *msgs.UpdateNamespaceRequest) msgs.UpdateNamespaceResponse { - log.Debugf("UpdateNamespace %v", request) resp := msgs.UpdateNamespaceResponse{} resp.Status.Code = msgs.Ok resp.Status.Msg = "" resp.Results = make([]string, 0) - //iterate thru all the args (namespace names) + // iterate thru all the args (namespace names) for _, namespace := range request.Args { if err := ns.UpdateNamespace(clientset, apiserver.InstallationName, @@ -162,5 +157,4 @@ func UpdateNamespace(clientset kubernetes.Interface, updatedBy string, request * } return resp - } diff --git a/internal/apiserver/namespaceservice/namespaceservice.go b/internal/apiserver/namespaceservice/namespaceservice.go index 1e27294c96..fa5918fe45 100644 --- a/internal/apiserver/namespaceservice/namespaceservice.go +++ b/internal/apiserver/namespaceservice/namespaceservice.go @@ -77,12 +77,12 @@ func ShowNamespaceHandler(w http.ResponseWriter, r *http.Request) { if request.ClientVersion != msgs.PGO_VERSION { resp.Status.Code = msgs.Error resp.Status.Msg = apiserver.VERSION_MISMATCH_ERROR - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = ShowNamespace(apiserver.Clientset, username, &request) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } func CreateNamespaceHandler(w http.ResponseWriter, r *http.Request) { @@ -132,12 +132,12 @@ func CreateNamespaceHandler(w http.ResponseWriter, r *http.Request) { if request.ClientVersion != msgs.PGO_VERSION { resp.Status.Code = msgs.Error resp.Status.Msg = apiserver.VERSION_MISMATCH_ERROR - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = CreateNamespace(apiserver.Clientset, username, &request) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } func DeleteNamespaceHandler(w http.ResponseWriter, r *http.Request) { @@ -187,14 +187,14 @@ func DeleteNamespaceHandler(w http.ResponseWriter, r *http.Request) { if request.ClientVersion != msgs.PGO_VERSION { resp.Status.Code = msgs.Error resp.Status.Msg = apiserver.VERSION_MISMATCH_ERROR - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = DeleteNamespace(apiserver.Clientset, username, &request) - json.NewEncoder(w).Encode(resp) - + _ = json.NewEncoder(w).Encode(resp) } + func UpdateNamespaceHandler(w http.ResponseWriter, r *http.Request) { // swagger:operation POST /namespaceupdate namespaceservice namespaceupdate /*``` @@ -242,10 +242,10 @@ func UpdateNamespaceHandler(w http.ResponseWriter, r *http.Request) { if request.ClientVersion != msgs.PGO_VERSION { resp.Status.Code = msgs.Error resp.Status.Msg = apiserver.VERSION_MISMATCH_ERROR - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = UpdateNamespace(apiserver.Clientset, username, &request) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } diff --git a/internal/apiserver/perms.go b/internal/apiserver/perms.go index 48c72099a6..71316b2c82 100644 --- a/internal/apiserver/perms.go +++ b/internal/apiserver/perms.go @@ -97,8 +97,10 @@ const ( UPDATE_USER_PERM = "UpdateUser" ) -var RoleMap map[string]map[string]string -var PermMap map[string]string +var ( + RoleMap map[string]map[string]string + PermMap map[string]string +) func initializePerms() { RoleMap = make(map[string]map[string]string) @@ -180,5 +182,4 @@ func initializePerms() { } log.Infof("loading PermMap with %d Permissions\n", len(PermMap)) - } diff --git a/internal/apiserver/pgadminservice/pgadminimpl.go b/internal/apiserver/pgadminservice/pgadminimpl.go index 4f23a8d028..89bb7bcde3 100644 --- a/internal/apiserver/pgadminservice/pgadminimpl.go +++ b/internal/apiserver/pgadminservice/pgadminimpl.go @@ -183,7 +183,6 @@ func ShowPgAdmin(request *msgs.ShowPgAdminRequest, namespace string) msgs.ShowPg // try to get the list of clusters. if there is an error, put it into the // status and return clusterList, err := getClusterList(request.Namespace, request.ClusterNames, request.Selector) - if err != nil { response.SetError(err.Error()) return response @@ -191,7 +190,8 @@ func ShowPgAdmin(request *msgs.ShowPgAdminRequest, namespace string) msgs.ShowPg // iterate through the list of clusters to get the relevant pgAdmin // information about them - for _, cluster := range clusterList.Items { + for i := range clusterList.Items { + cluster := &clusterList.Items[i] result := msgs.ShowPgAdminDetail{ ClusterName: cluster.Spec.Name, HasPgAdmin: true, @@ -228,7 +228,7 @@ func ShowPgAdmin(request *msgs.ShowPgAdminRequest, namespace string) msgs.ShowPg // In the future, construct results to contain individual error stati // for now log and return empty content if encountered - qr, err := pgadmin.GetPgAdminQueryRunner(apiserver.Clientset, apiserver.RESTConfig, &cluster) + qr, err := pgadmin.GetPgAdminQueryRunner(apiserver.Clientset, apiserver.RESTConfig, cluster) if err != nil { log.Error(err) continue @@ -267,8 +267,7 @@ func getClusterList(namespace string, clusterNames []string, selector string) (c cl, err := apiserver.Clientset. CrunchydataV1().Pgclusters(namespace). List(ctx, metav1.ListOptions{LabelSelector: selector}) - - // if there is an error, return here with an empty cluster list + // if there is an error, return here with an empty cluster list if err != nil { return crv1.PgclusterList{}, err } @@ -278,7 +277,6 @@ func getClusterList(namespace string, clusterNames []string, selector string) (c // now try to get clusters based specific cluster names for _, clusterName := range clusterNames { cluster, err := apiserver.Clientset.CrunchydataV1().Pgclusters(namespace).Get(ctx, clusterName, metav1.GetOptions{}) - // if there is an error, capture it here and return here with an empty list if err != nil { return crv1.PgclusterList{}, err diff --git a/internal/apiserver/pgadminservice/pgadminservice.go b/internal/apiserver/pgadminservice/pgadminservice.go index 90378868ca..68c1b1b3db 100644 --- a/internal/apiserver/pgadminservice/pgadminservice.go +++ b/internal/apiserver/pgadminservice/pgadminservice.go @@ -63,20 +63,19 @@ func CreatePgAdminHandler(w http.ResponseWriter, r *http.Request) { if request.ClientVersion != msgs.PGO_VERSION { resp.SetError(apiserver.VERSION_MISMATCH_ERROR) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } ns, err = apiserver.GetNamespace(apiserver.Clientset, username, request.Namespace) if err != nil { resp.SetError(err.Error()) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = CreatePgAdmin(&request, ns, username) - json.NewEncoder(w).Encode(resp) - + _ = json.NewEncoder(w).Encode(resp) } // DeletePgAdminHandler ... @@ -117,20 +116,19 @@ func DeletePgAdminHandler(w http.ResponseWriter, r *http.Request) { if request.ClientVersion != msgs.PGO_VERSION { resp.SetError(apiserver.VERSION_MISMATCH_ERROR) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } ns, err = apiserver.GetNamespace(apiserver.Clientset, username, request.Namespace) if err != nil { resp.SetError(err.Error()) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = DeletePgAdmin(&request, ns) - json.NewEncoder(w).Encode(resp) - + _ = json.NewEncoder(w).Encode(resp) } // ShowPgAdminHandler is the HTTP handler to get information about a pgBouncer @@ -173,21 +171,19 @@ func ShowPgAdminHandler(w http.ResponseWriter, r *http.Request) { // ensure the versions align... if request.ClientVersion != msgs.PGO_VERSION { resp.SetError(apiserver.VERSION_MISMATCH_ERROR) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } // ensure the namespace being used exists namespace, err := apiserver.GetNamespace(apiserver.Clientset, username, request.Namespace) - if err != nil { resp.SetError(err.Error()) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } // get the information about a pgAdmin deployment(s) resp = ShowPgAdmin(&request, namespace) - json.NewEncoder(w).Encode(resp) - + _ = json.NewEncoder(w).Encode(resp) } diff --git a/internal/apiserver/pgbouncerservice/pgbouncerimpl.go b/internal/apiserver/pgbouncerservice/pgbouncerimpl.go index 85373855d6..bd4b0e8fa4 100644 --- a/internal/apiserver/pgbouncerservice/pgbouncerimpl.go +++ b/internal/apiserver/pgbouncerservice/pgbouncerimpl.go @@ -72,14 +72,14 @@ func CreatePgbouncer(request *msgs.CreatePgbouncerRequest, ns, pgouser string) m // try to get the list of clusters. if there is an error, put it into the // status and return clusterList, err := getClusterList(request.Namespace, request.Args, request.Selector) - if err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() return resp } - for _, cluster := range clusterList.Items { + for i := range clusterList.Items { + cluster := clusterList.Items[i] // check if the current cluster is not upgraded to the deployed // Operator version. If not, do not allow the command to complete if cluster.Annotations[config.ANNOTATION_IS_UPGRADED] == config.ANNOTATIONS_FALSE { @@ -170,7 +170,6 @@ func DeletePgbouncer(request *msgs.DeletePgbouncerRequest, ns string) msgs.Delet // try to get the list of clusters. if there is an error, put it into the // status and return clusterList, err := getClusterList(request.Namespace, request.Args, request.Selector) - if err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() @@ -191,7 +190,8 @@ func DeletePgbouncer(request *msgs.DeletePgbouncerRequest, ns string) msgs.Delet return resp } - for _, cluster := range clusterList.Items { + for i := range clusterList.Items { + cluster := clusterList.Items[i] log.Debugf("deleting pgbouncer from cluster [%s]", cluster.Name) // check to see if the uninstall flag was set. If it was, apply the update @@ -226,7 +226,6 @@ func DeletePgbouncer(request *msgs.DeletePgbouncerRequest, ns string) msgs.Delet } return resp - } // ShowPgBouncer gets information about a PostgreSQL cluster's pgBouncer @@ -249,7 +248,6 @@ func ShowPgBouncer(request *msgs.ShowPgBouncerRequest, namespace string) msgs.Sh // try to get the list of clusters. if there is an error, put it into the // status and return clusterList, err := getClusterList(request.Namespace, request.ClusterNames, request.Selector) - if err != nil { response.Status.Code = msgs.Error response.Status.Msg = err.Error() @@ -331,7 +329,6 @@ func UpdatePgBouncer(request *msgs.UpdatePgBouncerRequest, namespace, pgouser st // try to get the list of clusters. if there is an error, put it into the // status and return clusterList, err := getClusterList(request.Namespace, request.ClusterNames, request.Selector) - if err != nil { response.Status.Code = msgs.Error response.Status.Msg = err.Error() @@ -352,7 +349,8 @@ func UpdatePgBouncer(request *msgs.UpdatePgBouncerRequest, namespace, pgouser st // iterate through the list of clusters to get the relevant pgBouncer // information about them - for _, cluster := range clusterList.Items { + for i := range clusterList.Items { + cluster := clusterList.Items[i] result := msgs.UpdatePgBouncerDetail{ ClusterName: cluster.Spec.Name, HasPgBouncer: true, @@ -449,7 +447,6 @@ func getClusterList(namespace string, clusterNames []string, selector string) (c // of arguments...or both. First, start with the selector if selector != "" { cl, err := apiserver.Clientset.CrunchydataV1().Pgclusters(namespace).List(ctx, metav1.ListOptions{LabelSelector: selector}) - // if there is an error, return here with an empty cluster list if err != nil { return crv1.PgclusterList{}, err @@ -460,7 +457,6 @@ func getClusterList(namespace string, clusterNames []string, selector string) (c // now try to get clusters based specific cluster names for _, clusterName := range clusterNames { cluster, err := apiserver.Clientset.CrunchydataV1().Pgclusters(namespace).Get(ctx, clusterName, metav1.GetOptions{}) - // if there is an error, capture it here and return here with an empty list if err != nil { return crv1.PgclusterList{}, err @@ -490,7 +486,6 @@ func setPgBouncerPasswordDetail(cluster crv1.Pgcluster, result *msgs.ShowPgBounc // attempt to get the secret, but only get the password password, err := util.GetPasswordFromSecret(apiserver.Clientset, cluster.Spec.Namespace, pgBouncerSecretName) - if err != nil { log.Warn(err) } @@ -510,8 +505,7 @@ func setPgBouncerServiceDetail(cluster crv1.Pgcluster, result *msgs.ShowPgBounce services, err := apiserver.Clientset. CoreV1().Services(cluster.Spec.Namespace). List(ctx, metav1.ListOptions{LabelSelector: selector}) - - // if there is an error, return without making any adjustments + // if there is an error, return without making any adjustments if err != nil { log.Warn(err) return diff --git a/internal/apiserver/pgbouncerservice/pgbouncerservice.go b/internal/apiserver/pgbouncerservice/pgbouncerservice.go index 969aabd205..773514d48d 100644 --- a/internal/apiserver/pgbouncerservice/pgbouncerservice.go +++ b/internal/apiserver/pgbouncerservice/pgbouncerservice.go @@ -17,10 +17,11 @@ limitations under the License. import ( "encoding/json" + "net/http" + "github.com/crunchydata/postgres-operator/internal/apiserver" msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" - "net/http" ) // CreatePgbouncerHandler ... @@ -63,7 +64,7 @@ func CreatePgbouncerHandler(w http.ResponseWriter, r *http.Request) { if request.ClientVersion != msgs.PGO_VERSION { resp.Status.Code = msgs.Error resp.Status.Msg = apiserver.VERSION_MISMATCH_ERROR - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } @@ -71,13 +72,12 @@ func CreatePgbouncerHandler(w http.ResponseWriter, r *http.Request) { if err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = CreatePgbouncer(&request, ns, username) - json.NewEncoder(w).Encode(resp) - + _ = json.NewEncoder(w).Encode(resp) } /* The delete pgboucner handler is setup to be used by two different routes. To keep @@ -141,7 +141,7 @@ func DeletePgbouncerHandler(w http.ResponseWriter, r *http.Request) { if request.ClientVersion != msgs.PGO_VERSION { resp.Status.Code = msgs.Error resp.Status.Msg = apiserver.VERSION_MISMATCH_ERROR - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } @@ -149,13 +149,12 @@ func DeletePgbouncerHandler(w http.ResponseWriter, r *http.Request) { if err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = DeletePgbouncer(&request, ns) - json.NewEncoder(w).Encode(resp) - + _ = json.NewEncoder(w).Encode(resp) } // ShowPgBouncerHandler is the HTTP handler to get information about a pgBouncer @@ -182,7 +181,6 @@ func ShowPgBouncerHandler(w http.ResponseWriter, r *http.Request) { // first, determine if the user is authorized to access this resource username, err := apiserver.Authn(apiserver.SHOW_PGBOUNCER_PERM, w, r) - if err != nil { return } @@ -202,13 +200,12 @@ func ShowPgBouncerHandler(w http.ResponseWriter, r *http.Request) { Msg: apiserver.VERSION_MISMATCH_ERROR, }, } - json.NewEncoder(w).Encode(response) + _ = json.NewEncoder(w).Encode(response) return } // ensure the namespace being used exists namespace, err := apiserver.GetNamespace(apiserver.Clientset, username, request.Namespace) - if err != nil { response := msgs.ShowPgBouncerResponse{ Status: msgs.Status{ @@ -216,14 +213,13 @@ func ShowPgBouncerHandler(w http.ResponseWriter, r *http.Request) { Msg: err.Error(), }, } - json.NewEncoder(w).Encode(response) + _ = json.NewEncoder(w).Encode(response) return } // get the information about a pgbouncer deployment(s) response := ShowPgBouncer(&request, namespace) - json.NewEncoder(w).Encode(response) - + _ = json.NewEncoder(w).Encode(response) } // UpdatePgBouncerHandler is the HTTP handler to perform update tasks on a @@ -250,7 +246,6 @@ func UpdatePgBouncerHandler(w http.ResponseWriter, r *http.Request) { // first, determine if the user is authorized to access this resource username, err := apiserver.Authn(apiserver.UPDATE_PGBOUNCER_PERM, w, r) - if err != nil { return } @@ -270,13 +265,12 @@ func UpdatePgBouncerHandler(w http.ResponseWriter, r *http.Request) { Msg: apiserver.VERSION_MISMATCH_ERROR, }, } - json.NewEncoder(w).Encode(response) + _ = json.NewEncoder(w).Encode(response) return } // ensure the namespace being used exists namespace, err := apiserver.GetNamespace(apiserver.Clientset, username, request.Namespace) - if err != nil { response := msgs.UpdatePgBouncerResponse{ Status: msgs.Status{ @@ -284,11 +278,11 @@ func UpdatePgBouncerHandler(w http.ResponseWriter, r *http.Request) { Msg: err.Error(), }, } - json.NewEncoder(w).Encode(response) + _ = json.NewEncoder(w).Encode(response) return } // get the information about a pgbouncer deployment(s) response := UpdatePgBouncer(&request, namespace, username) - json.NewEncoder(w).Encode(response) + _ = json.NewEncoder(w).Encode(response) } diff --git a/internal/apiserver/pgdumpservice/pgdumpimpl.go b/internal/apiserver/pgdumpservice/pgdumpimpl.go index 4a5f1a5d42..5a93045493 100644 --- a/internal/apiserver/pgdumpservice/pgdumpimpl.go +++ b/internal/apiserver/pgdumpservice/pgdumpimpl.go @@ -17,7 +17,6 @@ limitations under the License. import ( "context" - "errors" "fmt" "strconv" "strings" @@ -28,13 +27,14 @@ import ( crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" - v1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -const pgDumpTaskExtension = "-pgdump" -const pgDumpJobExtension = "-pgdump-job" +const ( + pgDumpTaskExtension = "-pgdump" + pgDumpJobExtension = "-pgdump-job" +) // CreateBackup ... // pgo backup mycluster @@ -50,7 +50,7 @@ func CreatepgDump(request *msgs.CreatepgDumpBackupRequest, ns string) msgs.Creat log.Debug("CreatePgDump storage config... " + request.StorageConfig) if request.StorageConfig != "" { - if apiserver.IsValidStorageName(request.StorageConfig) == false { + if !apiserver.IsValidStorageName(request.StorageConfig) { log.Debug("CreateBackup sc error is found " + request.StorageConfig) resp.Status.Code = msgs.Error resp.Status.Msg = request.StorageConfig + " Storage config was not found " @@ -68,7 +68,7 @@ func CreatepgDump(request *msgs.CreatepgDumpBackupRequest, ns string) msgs.Creat } if request.Selector != "" { - //use the selector instead of an argument list to filter on + // use the selector instead of an argument list to filter on clusterList, err := apiserver.Clientset. CrunchydataV1().Pgclusters(ns). @@ -117,7 +117,7 @@ func CreatepgDump(request *msgs.CreatepgDumpBackupRequest, ns string) msgs.Creat } deletePropagation := metav1.DeletePropagationForeground - apiserver.Clientset. + _ = apiserver.Clientset. BatchV1().Jobs(ns). Delete(ctx, clusterName+pgDumpJobExtension, metav1.DeleteOptions{PropagationPolicy: &deletePropagation}) @@ -132,9 +132,8 @@ func CreatepgDump(request *msgs.CreatepgDumpBackupRequest, ns string) msgs.Creat } else { log.Debugf("pgtask %s was found so we will recreate it", taskName) - //remove the existing pgtask + // remove the existing pgtask err := apiserver.Clientset.CrunchydataV1().Pgtasks(ns).Delete(ctx, taskName, metav1.DeleteOptions{}) - if err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() @@ -142,21 +141,9 @@ func CreatepgDump(request *msgs.CreatepgDumpBackupRequest, ns string) msgs.Creat } } - //get pod name from cluster - // var podname, deployName string - var podname string - podname, err = getPrimaryPodName(cluster, ns) - - if err != nil { - log.Error(err) - resp.Status.Code = msgs.Error - resp.Status.Msg = err.Error() - return resp - } - // where all the magic happens about the task. // TODO: Needs error handling for invalid parameters in the request - theTask := buildPgTaskForDump(clusterName, taskName, crv1.PgtaskpgDump, podname, "database", request) + theTask := buildPgTaskForDump(clusterName, taskName, crv1.PgtaskpgDump, "database", request) _, err = apiserver.Clientset.CrunchydataV1().Pgtasks(ns).Create(ctx, theTask, metav1.CreateOptions{}) if err != nil { @@ -194,7 +181,7 @@ func ShowpgDump(clusterName string, selector string, ns string) msgs.ShowBackupR } } - //get a list of all clusters + // get a list of all clusters clusterList, err := apiserver.Clientset. CrunchydataV1().Pgclusters(ns). List(ctx, metav1.ListOptions{LabelSelector: selector}) @@ -217,7 +204,7 @@ func ShowpgDump(clusterName string, selector string, ns string) msgs.ShowBackupR pgTaskName := "backup-" + c.Name + pgDumpTaskExtension - backupItem, error := getPgBackupForTask(c.Name, pgTaskName, ns) + backupItem, error := getPgBackupForTask(pgTaskName, ns) if backupItem != nil { log.Debugf("pgTask %s was found", pgTaskName) @@ -238,13 +225,11 @@ func ShowpgDump(clusterName string, selector string, ns string) msgs.ShowBackupR } return response - } // builds out a pgTask structure that can be handed to kube -func buildPgTaskForDump(clusterName, taskName, action, podName, containerName string, +func buildPgTaskForDump(clusterName, taskName, action, containerName string, request *msgs.CreatepgDumpBackupRequest) *crv1.Pgtask { - var newInstance *crv1.Pgtask var storageSpec crv1.PgStorageSpec var pvcName string @@ -298,50 +283,6 @@ func buildPgTaskForDump(clusterName, taskName, action, podName, containerName st return newInstance } -func getPrimaryPodName(cluster *crv1.Pgcluster, ns string) (string, error) { - ctx := context.TODO() - var podname string - - selector := config.LABEL_SERVICE_NAME + "=" + cluster.Spec.Name - - pods, err := apiserver.Clientset.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{LabelSelector: selector}) - if err != nil { - return podname, err - } - - for _, p := range pods.Items { - if isPrimary(&p, cluster.Spec.Name) && isReady(&p) { - return p.Name, err - } - } - - return podname, errors.New("primary pod is not in Ready state") -} - -func isPrimary(pod *v1.Pod, clusterName string) bool { - if pod.ObjectMeta.Labels[config.LABEL_SERVICE_NAME] == clusterName { - return true - } - return false - -} - -func isReady(pod *v1.Pod) bool { - readyCount := 0 - containerCount := 0 - for _, stat := range pod.Status.ContainerStatuses { - containerCount++ - if stat.Ready { - readyCount++ - } - } - if readyCount != containerCount { - return false - } - return true - -} - // dumpAllFlag, dumpOpts = parseOptionFlags(request.BackupOpt) func parseOptionFlags(allFlags string) (bool, string) { dumpFlag := false @@ -353,14 +294,12 @@ func parseOptionFlags(allFlags string) (bool, string) { options := strings.Split(allFlags, " ") for _, token := range options { - // handle dump flag if strings.Contains(token, "--dump-all") { dumpFlag = true } else { parsedOptions = append(parsedOptions, token) } - } optionString := strings.Join(parsedOptions, " ") @@ -368,11 +307,10 @@ func parseOptionFlags(allFlags string) (bool, string) { log.Debugf("pgdump optionFlags: %s, dumpAll: %t", optionString, dumpFlag) return dumpFlag, optionString - } // if backup && err are nil, it simply wasn't found. Otherwise found or an error -func getPgBackupForTask(clusterName string, taskName string, ns string) (*msgs.Pgbackup, error) { +func getPgBackupForTask(taskName, ns string) (*msgs.Pgbackup, error) { ctx := context.TODO() task, err := apiserver.Clientset.CrunchydataV1().Pgtasks(ns).Get(ctx, taskName, metav1.GetOptions{}) @@ -388,7 +326,6 @@ func getPgBackupForTask(clusterName string, taskName string, ns string) (*msgs.P // converts pgTask to a pgBackup structure func buildPgBackupFrompgTask(dumpTask *crv1.Pgtask) *msgs.Pgbackup { - backup := msgs.Pgbackup{} spec := dumpTask.Spec @@ -461,7 +398,7 @@ func Restore(request *msgs.PgRestoreRequest, ns string) msgs.PgRestoreResponse { return resp } - //delete any existing pgtask with the same name + // delete any existing pgtask with the same name err = apiserver.Clientset.CrunchydataV1().Pgtasks(ns).Delete(ctx, pgtask.Name, metav1.DeleteOptions{}) if err != nil && !kerrors.IsNotFound(err) { resp.Status.Code = msgs.Error @@ -469,7 +406,7 @@ func Restore(request *msgs.PgRestoreRequest, ns string) msgs.PgRestoreResponse { return resp } - //create a pgtask for the restore workflow + // create a pgtask for the restore workflow _, err = apiserver.Clientset.CrunchydataV1().Pgtasks(ns).Create(ctx, pgtask, metav1.CreateOptions{}) if err != nil { resp.Status.Code = msgs.Error @@ -484,7 +421,6 @@ func Restore(request *msgs.PgRestoreRequest, ns string) msgs.PgRestoreResponse { // builds out a pgTask structure that can be handed to kube func buildPgTaskForRestore(taskName string, action string, request *msgs.PgRestoreRequest) (*crv1.Pgtask, error) { - var newInstance *crv1.Pgtask var storageSpec crv1.PgStorageSpec diff --git a/internal/apiserver/pgdumpservice/pgdumpservice.go b/internal/apiserver/pgdumpservice/pgdumpservice.go index 755a9bbd98..0b57ed2d75 100644 --- a/internal/apiserver/pgdumpservice/pgdumpservice.go +++ b/internal/apiserver/pgdumpservice/pgdumpservice.go @@ -17,12 +17,13 @@ limitations under the License. import ( "encoding/json" + "net/http" + "github.com/crunchydata/postgres-operator/internal/apiserver" "github.com/crunchydata/postgres-operator/internal/config" msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" - "net/http" ) // BackupHandler ... @@ -66,12 +67,12 @@ func BackupHandler(w http.ResponseWriter, r *http.Request) { ns, err = apiserver.GetNamespace(apiserver.Clientset, username, request.Namespace) if err != nil { resp.Status = msgs.Status{Code: msgs.Error, Msg: err.Error()} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = CreatepgDump(&request, ns) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } // ShowpgDumpHandler ... @@ -135,7 +136,7 @@ func ShowDumpHandler(w http.ResponseWriter, r *http.Request) { if clientVersion != msgs.PGO_VERSION { resp.Status = msgs.Status{Code: msgs.Error, Msg: apiserver.VERSION_MISMATCH_ERROR} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } @@ -143,13 +144,12 @@ func ShowDumpHandler(w http.ResponseWriter, r *http.Request) { ns, err = apiserver.GetNamespace(apiserver.Clientset, username, namespace) if err != nil { resp.Status = msgs.Status{Code: msgs.Error, Msg: err.Error()} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = ShowpgDump(clustername, selector, ns) - json.NewEncoder(w).Encode(resp) - + _ = json.NewEncoder(w).Encode(resp) } // RestoreHandler ... @@ -195,7 +195,7 @@ func RestoreHandler(w http.ResponseWriter, r *http.Request) { if err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } @@ -205,5 +205,5 @@ func RestoreHandler(w http.ResponseWriter, r *http.Request) { resp.Status.Msg = err.Error() } - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } diff --git a/internal/apiserver/pgoroleservice/pgoroleimpl.go b/internal/apiserver/pgoroleservice/pgoroleimpl.go index 47f3c11502..4192a4437b 100644 --- a/internal/apiserver/pgoroleservice/pgoroleimpl.go +++ b/internal/apiserver/pgoroleservice/pgoroleimpl.go @@ -34,7 +34,6 @@ import ( // CreatePgorole ... func CreatePgorole(clientset kubernetes.Interface, createdBy string, request *msgs.CreatePgoroleRequest) msgs.CreatePgoroleResponse { - log.Debugf("CreatePgorole %v", request) resp := msgs.CreatePgoroleResponse{} resp.Status.Code = msgs.Ok @@ -54,7 +53,7 @@ func CreatePgorole(clientset kubernetes.Interface, createdBy string, request *ms return resp } - //publish event + // publish event topics := make([]string, 1) topics[0] = events.EventTopicPGOUser @@ -77,7 +76,6 @@ func CreatePgorole(clientset kubernetes.Interface, createdBy string, request *ms } return resp - } // ShowPgorole ... @@ -122,7 +120,6 @@ func ShowPgorole(clientset kubernetes.Interface, request *msgs.ShowPgoroleReques } return resp - } // DeletePgorole ... @@ -164,7 +161,6 @@ func DeletePgorole(clientset kubernetes.Interface, deletedBy string, request *ms } return resp - } func UpdatePgorole(clientset kubernetes.Interface, updatedBy string, request *msgs.UpdatePgoroleRequest) msgs.UpdatePgoroleResponse { @@ -200,7 +196,7 @@ func UpdatePgorole(clientset kubernetes.Interface, updatedBy string, request *ms return resp } - //publish event + // publish event topics := make([]string, 1) topics[0] = events.EventTopicPGOUser @@ -223,13 +219,12 @@ func UpdatePgorole(clientset kubernetes.Interface, updatedBy string, request *ms } return resp - } func createSecret(clientset kubernetes.Interface, createdBy, pgorolename, permissions string) error { ctx := context.TODO() - var enRolename = pgorolename + enRolename := pgorolename secretName := "pgorole-" + pgorolename @@ -269,7 +264,7 @@ func validPermissions(perms string) error { func deleteRoleFromUsers(clientset kubernetes.Interface, roleName string) error { ctx := context.TODO() - //get pgouser Secrets + // get pgouser Secrets selector := config.LABEL_PGO_PGOUSER + "=true" pgouserSecrets, err := clientset. @@ -280,7 +275,8 @@ func deleteRoleFromUsers(clientset kubernetes.Interface, roleName string) error return err } - for _, s := range pgouserSecrets.Items { + for i := range pgouserSecrets.Items { + s := &pgouserSecrets.Items[i] rolesString := string(s.Data[pgouserservice.MAP_KEY_ROLES]) roles := strings.Split(rolesString, ",") resultRoles := make([]string, 0) @@ -294,7 +290,7 @@ func deleteRoleFromUsers(clientset kubernetes.Interface, roleName string) error } } - //update the pgouser Secret removing any roles as necessary + // update the pgouser Secret removing any roles as necessary if rolesUpdated { var resultingRoleString string @@ -307,8 +303,7 @@ func deleteRoleFromUsers(clientset kubernetes.Interface, roleName string) error } s.Data[pgouserservice.MAP_KEY_ROLES] = []byte(resultingRoleString) - _, err = clientset.CoreV1().Secrets(apiserver.PgoNamespace).Update(ctx, &s, metav1.UpdateOptions{}) - if err != nil { + if _, err := clientset.CoreV1().Secrets(apiserver.PgoNamespace).Update(ctx, s, metav1.UpdateOptions{}); err != nil { return err } diff --git a/internal/apiserver/pgoroleservice/pgoroleservice.go b/internal/apiserver/pgoroleservice/pgoroleservice.go index b3e3413e09..1ab26cdac6 100644 --- a/internal/apiserver/pgoroleservice/pgoroleservice.go +++ b/internal/apiserver/pgoroleservice/pgoroleservice.go @@ -17,11 +17,12 @@ limitations under the License. import ( "encoding/json" + "net/http" + apiserver "github.com/crunchydata/postgres-operator/internal/apiserver" msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/util/validation" - "net/http" ) func CreatePgoroleHandler(w http.ResponseWriter, r *http.Request) { @@ -63,7 +64,7 @@ func CreatePgoroleHandler(w http.ResponseWriter, r *http.Request) { if request.ClientVersion != msgs.PGO_VERSION { resp.Status.Code = msgs.Error resp.Status.Msg = apiserver.VERSION_MISMATCH_ERROR - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } @@ -75,7 +76,7 @@ func CreatePgoroleHandler(w http.ResponseWriter, r *http.Request) { resp = CreatePgorole(apiserver.Clientset, rolename, &request) } - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } func DeletePgoroleHandler(w http.ResponseWriter, r *http.Request) { @@ -117,14 +118,13 @@ func DeletePgoroleHandler(w http.ResponseWriter, r *http.Request) { if request.ClientVersion != msgs.PGO_VERSION { resp.Status.Code = msgs.Error resp.Status.Msg = apiserver.VERSION_MISMATCH_ERROR - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = DeletePgorole(apiserver.Clientset, rolename, &request) - json.NewEncoder(w).Encode(resp) - + _ = json.NewEncoder(w).Encode(resp) } func ShowPgoroleHandler(w http.ResponseWriter, r *http.Request) { @@ -167,14 +167,13 @@ func ShowPgoroleHandler(w http.ResponseWriter, r *http.Request) { if request.ClientVersion != msgs.PGO_VERSION { resp.Status.Code = msgs.Error resp.Status.Msg = apiserver.VERSION_MISMATCH_ERROR - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = ShowPgorole(apiserver.Clientset, &request) - json.NewEncoder(w).Encode(resp) - + _ = json.NewEncoder(w).Encode(resp) } func UpdatePgoroleHandler(w http.ResponseWriter, r *http.Request) { @@ -213,5 +212,5 @@ func UpdatePgoroleHandler(w http.ResponseWriter, r *http.Request) { resp.Status = msgs.Status{Code: msgs.Ok, Msg: ""} resp = UpdatePgorole(apiserver.Clientset, rolename, &request) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } diff --git a/internal/apiserver/pgouserservice/pgouserimpl.go b/internal/apiserver/pgouserservice/pgouserimpl.go index aaa94fbc00..136deb2064 100644 --- a/internal/apiserver/pgouserservice/pgouserimpl.go +++ b/internal/apiserver/pgouserservice/pgouserimpl.go @@ -32,14 +32,15 @@ import ( "k8s.io/client-go/kubernetes" ) -const MAP_KEY_USERNAME = "username" -const MAP_KEY_PASSWORD = "password" -const MAP_KEY_ROLES = "roles" -const MAP_KEY_NAMESPACES = "namespaces" +const ( + MAP_KEY_USERNAME = "username" + MAP_KEY_PASSWORD = "password" + MAP_KEY_ROLES = "roles" + MAP_KEY_NAMESPACES = "namespaces" +) // CreatePgouser ... func CreatePgouser(clientset kubernetes.Interface, createdBy string, request *msgs.CreatePgouserRequest) msgs.CreatePgouserResponse { - log.Debugf("CreatePgouser %v", request) resp := msgs.CreatePgouserResponse{} resp.Status.Code = msgs.Ok @@ -71,7 +72,7 @@ func CreatePgouser(clientset kubernetes.Interface, createdBy string, request *ms return resp } - //publish event + // publish event topics := make([]string, 1) topics[0] = events.EventTopicPGOUser @@ -94,7 +95,6 @@ func CreatePgouser(clientset kubernetes.Interface, createdBy string, request *ms } return resp - } // ShowPgouser ... @@ -147,7 +147,6 @@ func ShowPgouser(clientset kubernetes.Interface, request *msgs.ShowPgouserReques } return resp - } // DeletePgouser ... @@ -170,7 +169,7 @@ func DeletePgouser(clientset kubernetes.Interface, deletedBy string, request *ms resp.Results = append(resp.Results, "error deleting secret "+secretName) } else { resp.Results = append(resp.Results, "deleted pgouser "+v) - //publish event + // publish event topics := make([]string, 1) topics[0] = events.EventTopicPGOUser @@ -198,7 +197,6 @@ func DeletePgouser(clientset kubernetes.Interface, deletedBy string, request *ms } return resp - } // UpdatePgouser - update the pgouser secret @@ -253,7 +251,7 @@ func UpdatePgouser(clientset kubernetes.Interface, updatedBy string, request *ms return resp } - //publish event + // publish event topics := make([]string, 1) topics[0] = events.EventTopicPGOUser @@ -275,7 +273,6 @@ func UpdatePgouser(clientset kubernetes.Interface, updatedBy string, request *ms } return resp - } func createSecret(clientset kubernetes.Interface, createdBy string, request *msgs.CreatePgouserRequest) error { @@ -323,7 +320,6 @@ func validRoles(clientset kubernetes.Interface, roles string) error { } func validNamespaces(namespaces string, allnamespaces bool) error { - if allnamespaces { return nil } diff --git a/internal/apiserver/pgouserservice/pgouserservice.go b/internal/apiserver/pgouserservice/pgouserservice.go index ccf1b1ce8f..f0205c5eea 100644 --- a/internal/apiserver/pgouserservice/pgouserservice.go +++ b/internal/apiserver/pgouserservice/pgouserservice.go @@ -17,11 +17,12 @@ limitations under the License. import ( "encoding/json" + "net/http" + apiserver "github.com/crunchydata/postgres-operator/internal/apiserver" msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/util/validation" - "net/http" ) func CreatePgouserHandler(w http.ResponseWriter, r *http.Request) { @@ -63,7 +64,7 @@ func CreatePgouserHandler(w http.ResponseWriter, r *http.Request) { if request.ClientVersion != msgs.PGO_VERSION { resp.Status.Code = msgs.Error resp.Status.Msg = apiserver.VERSION_MISMATCH_ERROR - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } @@ -75,7 +76,7 @@ func CreatePgouserHandler(w http.ResponseWriter, r *http.Request) { resp = CreatePgouser(apiserver.Clientset, username, &request) } - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } func DeletePgouserHandler(w http.ResponseWriter, r *http.Request) { @@ -117,14 +118,13 @@ func DeletePgouserHandler(w http.ResponseWriter, r *http.Request) { if request.ClientVersion != msgs.PGO_VERSION { resp.Status.Code = msgs.Error resp.Status.Msg = apiserver.VERSION_MISMATCH_ERROR - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = DeletePgouser(apiserver.Clientset, username, &request) - json.NewEncoder(w).Encode(resp) - + _ = json.NewEncoder(w).Encode(resp) } func ShowPgouserHandler(w http.ResponseWriter, r *http.Request) { @@ -167,14 +167,13 @@ func ShowPgouserHandler(w http.ResponseWriter, r *http.Request) { if request.ClientVersion != msgs.PGO_VERSION { resp.Status.Code = msgs.Error resp.Status.Msg = apiserver.VERSION_MISMATCH_ERROR - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = ShowPgouser(apiserver.Clientset, &request) - json.NewEncoder(w).Encode(resp) - + _ = json.NewEncoder(w).Encode(resp) } func UpdatePgouserHandler(w http.ResponseWriter, r *http.Request) { @@ -213,5 +212,5 @@ func UpdatePgouserHandler(w http.ResponseWriter, r *http.Request) { resp.Status = msgs.Status{Code: msgs.Ok, Msg: ""} resp = UpdatePgouser(apiserver.Clientset, username, &request) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } diff --git a/internal/apiserver/policyservice/policyimpl.go b/internal/apiserver/policyservice/policyimpl.go index e0302eeb6c..a3153536bc 100644 --- a/internal/apiserver/policyservice/policyimpl.go +++ b/internal/apiserver/policyservice/policyimpl.go @@ -68,7 +68,6 @@ func CreatePolicy(client pgo.Interface, policyName, policyURL, policyFile, ns, p } return false, err - } // ShowPolicy ... @@ -77,7 +76,7 @@ func ShowPolicy(client pgo.Interface, name string, allflags bool, ns string) crv policyList := crv1.PgpolicyList{} if allflags { - //get a list of all policies + // get a list of all policies list, err := client.CrunchydataV1().Pgpolicies(ns).List(ctx, metav1.ListOptions{}) if list != nil && err == nil { policyList = *list @@ -90,7 +89,6 @@ func ShowPolicy(client pgo.Interface, name string, allflags bool, ns string) crv } return policyList - } // DeletePolicy ... @@ -110,18 +108,19 @@ func DeletePolicy(client pgo.Interface, policyName, ns, pgouser string) msgs.Del policyFound := false log.Debugf("deleting policy %s", policyName) - for _, policy := range policyList.Items { + for i := range policyList.Items { + policy := &policyList.Items[i] if policyName == "all" || policyName == policy.Spec.Name { - //update pgpolicy with current pgouser so that - //we can create an event holding the pgouser - //that deleted the policy + // update pgpolicy with current pgouser so that + // we can create an event holding the pgouser + // that deleted the policy policy.ObjectMeta.Labels[config.LABEL_PGOUSER] = pgouser - _, err = client.CrunchydataV1().Pgpolicies(ns).Update(ctx, &policy, metav1.UpdateOptions{}) + _, err = client.CrunchydataV1().Pgpolicies(ns).Update(ctx, policy, metav1.UpdateOptions{}) if err != nil { log.Error(err) } - //ok, now delete the pgpolicy + // ok, now delete the pgpolicy policyFound = true err = client.CrunchydataV1().Pgpolicies(ns).Delete(ctx, policy.Spec.Name, metav1.DeleteOptions{}) if err == nil { @@ -145,7 +144,6 @@ func DeletePolicy(client pgo.Interface, policyName, ns, pgouser string) msgs.Del } return resp - } // ApplyPolicy ... @@ -159,7 +157,7 @@ func ApplyPolicy(request *msgs.ApplyPolicyRequest, ns, pgouser string) msgs.Appl resp.Status.Msg = "" resp.Status.Code = msgs.Ok - //validate policy + // validate policy err = util.ValidatePolicy(apiserver.Clientset, ns, request.Name) if err != nil { resp.Status.Code = msgs.Error @@ -167,11 +165,11 @@ func ApplyPolicy(request *msgs.ApplyPolicyRequest, ns, pgouser string) msgs.Appl return resp } - //get filtered list of Deployments + // get filtered list of Deployments selector := request.Selector log.Debugf("apply policy selector string=[%s]", selector) - //get a list of all clusters + // get a list of all clusters clusterList, err := apiserver.Clientset. CrunchydataV1().Pgclusters(ns). List(ctx, metav1.ListOptions{LabelSelector: selector}) @@ -232,7 +230,7 @@ func ApplyPolicy(request *msgs.ApplyPolicyRequest, ns, pgouser string) msgs.Appl if d.ObjectMeta.Labels[config.LABEL_SERVICE_NAME] != d.ObjectMeta.Labels[config.LABEL_PG_CLUSTER] { log.Debugf("skipping apply policy on deployment %s", d.Name) continue - //skip non primary deployments + // skip non primary deployments } log.Debugf("apply policy %s on deployment %s based on selector %s", request.Name, d.ObjectMeta.Name, selector) @@ -261,7 +259,7 @@ func ApplyPolicy(request *msgs.ApplyPolicyRequest, ns, pgouser string) msgs.Appl log.Error(err) } - //update the pgcluster crd labels with the new policy + // update the pgcluster crd labels with the new policy log.Debugf("patching cluster %s: %s", cl.Name, patch) _, err = apiserver.Clientset.CrunchydataV1().Pgclusters(ns). Patch(ctx, cl.Name, types.MergePatchType, patch, metav1.PatchOptions{}) @@ -271,7 +269,7 @@ func ApplyPolicy(request *msgs.ApplyPolicyRequest, ns, pgouser string) msgs.Appl resp.Name = append(resp.Name, d.ObjectMeta.Name) - //publish event + // publish event topics := make([]string, 1) topics[0] = events.EventTopicPolicy @@ -294,5 +292,4 @@ func ApplyPolicy(request *msgs.ApplyPolicyRequest, ns, pgouser string) msgs.Appl } return resp - } diff --git a/internal/apiserver/policyservice/policyservice.go b/internal/apiserver/policyservice/policyservice.go index d2a3d6234f..4324faac21 100644 --- a/internal/apiserver/policyservice/policyservice.go +++ b/internal/apiserver/policyservice/policyservice.go @@ -65,7 +65,7 @@ func CreatePolicyHandler(w http.ResponseWriter, r *http.Request) { if request.ClientVersion != msgs.PGO_VERSION { resp.Status.Code = msgs.Error resp.Status.Msg = apiserver.VERSION_MISMATCH_ERROR - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } @@ -73,7 +73,7 @@ func CreatePolicyHandler(w http.ResponseWriter, r *http.Request) { if err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } @@ -95,7 +95,7 @@ func CreatePolicyHandler(w http.ResponseWriter, r *http.Request) { } } - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } // DeletePolicyHandler ... @@ -145,7 +145,7 @@ func DeletePolicyHandler(w http.ResponseWriter, r *http.Request) { if clientVersion != msgs.PGO_VERSION { resp.Status.Code = msgs.Error resp.Status.Msg = apiserver.VERSION_MISMATCH_ERROR - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } @@ -153,14 +153,13 @@ func DeletePolicyHandler(w http.ResponseWriter, r *http.Request) { if err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = DeletePolicy(apiserver.Clientset, policyname, ns, username) - json.NewEncoder(w).Encode(resp) - + _ = json.NewEncoder(w).Encode(resp) } // ShowPolicyHandler ... @@ -212,7 +211,7 @@ func ShowPolicyHandler(w http.ResponseWriter, r *http.Request) { if clientVersion != msgs.PGO_VERSION { resp.Status.Code = msgs.Error resp.Status.Msg = apiserver.VERSION_MISMATCH_ERROR - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } @@ -220,14 +219,13 @@ func ShowPolicyHandler(w http.ResponseWriter, r *http.Request) { if err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp.PolicyList = ShowPolicy(apiserver.Clientset, policyname, request.AllFlag, ns) - json.NewEncoder(w).Encode(resp) - + _ = json.NewEncoder(w).Encode(resp) } // ApplyPolicyHandler ... @@ -271,10 +269,10 @@ func ApplyPolicyHandler(w http.ResponseWriter, r *http.Request) { ns, err = apiserver.GetNamespace(apiserver.Clientset, username, request.Namespace) if err != nil { resp.Status = msgs.Status{Code: msgs.Error, Msg: err.Error()} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = ApplyPolicy(&request, ns, username) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } diff --git a/internal/apiserver/pvcservice/pvcservice.go b/internal/apiserver/pvcservice/pvcservice.go index a12979cb3c..332b3740b2 100644 --- a/internal/apiserver/pvcservice/pvcservice.go +++ b/internal/apiserver/pvcservice/pvcservice.go @@ -17,10 +17,11 @@ limitations under the License. import ( "encoding/json" + "net/http" + "github.com/crunchydata/postgres-operator/internal/apiserver" msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" - "net/http" ) // ShowPVCHandler ... @@ -76,7 +77,7 @@ func ShowPVCHandler(w http.ResponseWriter, r *http.Request) { if clientVersion != msgs.PGO_VERSION { resp.Status.Code = msgs.Error resp.Status.Msg = apiserver.VERSION_MISMATCH_ERROR - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } @@ -84,7 +85,7 @@ func ShowPVCHandler(w http.ResponseWriter, r *http.Request) { if err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } @@ -94,5 +95,5 @@ func ShowPVCHandler(w http.ResponseWriter, r *http.Request) { resp.Status.Msg = err.Error() } - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } diff --git a/internal/apiserver/reloadservice/reloadimpl.go b/internal/apiserver/reloadservice/reloadimpl.go index dba6e6cd8f..25ed88be67 100644 --- a/internal/apiserver/reloadservice/reloadimpl.go +++ b/internal/apiserver/reloadservice/reloadimpl.go @@ -116,7 +116,6 @@ func Reload(request *msgs.ReloadRequest, ns, username string) msgs.ReloadRespons // publishReloadClusterEvent publishes an event when a cluster is reloaded func publishReloadClusterEvent(clusterName, username, namespace string) error { - topics := make([]string, 1) topics[0] = events.EventTopicCluster diff --git a/internal/apiserver/reloadservice/reloadservice.go b/internal/apiserver/reloadservice/reloadservice.go index 9d1096c3c9..149b660125 100644 --- a/internal/apiserver/reloadservice/reloadservice.go +++ b/internal/apiserver/reloadservice/reloadservice.go @@ -17,10 +17,11 @@ limitations under the License. import ( "encoding/json" + "net/http" + "github.com/crunchydata/postgres-operator/internal/apiserver" msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" - "net/http" ) // ReloadHandler ... @@ -67,7 +68,7 @@ func ReloadHandler(w http.ResponseWriter, r *http.Request) { resp := msgs.ReloadResponse{} resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } @@ -76,9 +77,9 @@ func ReloadHandler(w http.ResponseWriter, r *http.Request) { resp := msgs.ReloadResponse{} resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } - json.NewEncoder(w).Encode(reloadResponse) + _ = json.NewEncoder(w).Encode(reloadResponse) } diff --git a/internal/apiserver/restartservice/restartservice.go b/internal/apiserver/restartservice/restartservice.go index a1bfb97194..374cb3ca93 100644 --- a/internal/apiserver/restartservice/restartservice.go +++ b/internal/apiserver/restartservice/restartservice.go @@ -56,14 +56,14 @@ func RestartHandler(w http.ResponseWriter, r *http.Request) { var request msgs.RestartRequest if err := json.NewDecoder(r.Body).Decode(&request); err != nil { resp.Status = msgs.Status{Code: msgs.Error, Msg: err.Error()} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } username, err := apiserver.Authn(apiserver.RESTART_PERM, w, r) if err != nil { resp.Status = msgs.Status{Code: msgs.Error, Msg: err.Error()} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } @@ -73,17 +73,17 @@ func RestartHandler(w http.ResponseWriter, r *http.Request) { if request.ClientVersion != msgs.PGO_VERSION { resp.Status = msgs.Status{Code: msgs.Error, Msg: apiserver.VERSION_MISMATCH_ERROR} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } if _, err := apiserver.GetNamespace(apiserver.Clientset, username, request.Namespace); err != nil { resp.Status = msgs.Status{Code: msgs.Error, Msg: err.Error()} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } - json.NewEncoder(w).Encode(Restart(&request, username)) + _ = json.NewEncoder(w).Encode(Restart(&request, username)) } // QueryRestartHandler handles requests to query a cluster for instances available to use as @@ -131,7 +131,7 @@ func QueryRestartHandler(w http.ResponseWriter, r *http.Request) { username, err := apiserver.Authn(apiserver.RESTART_PERM, w, r) if err != nil { resp.Status = msgs.Status{Code: msgs.Error, Msg: err.Error()} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } @@ -141,14 +141,14 @@ func QueryRestartHandler(w http.ResponseWriter, r *http.Request) { if clientVersion != msgs.PGO_VERSION { resp.Status = msgs.Status{Code: msgs.Error, Msg: apiserver.VERSION_MISMATCH_ERROR} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } if _, err := apiserver.GetNamespace(apiserver.Clientset, username, namespace); err != nil { resp.Status = msgs.Status{Code: msgs.Error, Msg: err.Error()} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } - json.NewEncoder(w).Encode(QueryRestart(clusterName, namespace)) + _ = json.NewEncoder(w).Encode(QueryRestart(clusterName, namespace)) } diff --git a/internal/apiserver/root.go b/internal/apiserver/root.go index f2a5ab149b..769ee79ab7 100644 --- a/internal/apiserver/root.go +++ b/internal/apiserver/root.go @@ -64,8 +64,10 @@ var DebugFlag bool var BasicAuth bool // Namespace comes from the apiserver config in this version -var PgoNamespace string -var InstallationName string +var ( + PgoNamespace string + InstallationName string +) var CRUNCHY_DEBUG bool @@ -90,7 +92,6 @@ var Pgo config.PgoConfig var namespaceOperatingMode ns.NamespaceOperatingMode func Initialize() { - PgoNamespace = os.Getenv("PGO_OPERATOR_NAMESPACE") if PgoNamespace == "" { log.Info("PGO_OPERATOR_NAMESPACE environment variable is not set and is required, this is the namespace that the Operator is to run within.") @@ -151,7 +152,6 @@ func Initialize() { } func connectToKube() { - client, err := kubeapi.NewClient() if err != nil { panic(err) @@ -193,14 +193,13 @@ func initConfig() { func BasicAuthCheck(username, password string) bool { ctx := context.TODO() - if BasicAuth == false { + if !BasicAuth { return true } - //see if there is a pgouser Secret for this username + // see if there is a pgouser Secret for this username secretName := "pgouser-" + username secret, err := Clientset.CoreV1().Secrets(PgoNamespace).Get(ctx, secretName, metav1.GetOptions{}) - if err != nil { log.Errorf("could not get pgouser secret %s: %s", username, err.Error()) return false @@ -213,13 +212,12 @@ func BasicAuthzCheck(username, perm string) bool { ctx := context.TODO() secretName := "pgouser-" + username secret, err := Clientset.CoreV1().Secrets(PgoNamespace).Get(ctx, secretName, metav1.GetOptions{}) - if err != nil { log.Errorf("could not get pgouser secret %s: %s", username, err.Error()) return false } - //get the roles for this user + // get the roles for this user rolesString := string(secret.Data["roles"]) roles := strings.Split(rolesString, ",") if len(roles) == 0 { @@ -227,13 +225,12 @@ func BasicAuthzCheck(username, perm string) bool { return false } - //venture thru each role this user has looking for a perm match + // venture thru each role this user has looking for a perm match for _, r := range roles { - //get the pgorole + // get the pgorole roleSecretName := "pgorole-" + r rolesecret, err := Clientset.CoreV1().Secrets(PgoNamespace).Get(ctx, roleSecretName, metav1.GetOptions{}) - if err != nil { log.Errorf("could not get pgorole secret %s: %s", r, err.Error()) return false @@ -262,14 +259,12 @@ func BasicAuthzCheck(username, perm string) bool { } return false - } -//GetNamespace determines if a user has permission for -//a namespace they are requesting -//a valid requested namespace is required +// GetNamespace determines if a user has permission for +// a namespace they are requesting +// a valid requested namespace is required func GetNamespace(clientset kubernetes.Interface, username, requestedNS string) (string, error) { - log.Debugf("GetNamespace username [%s] ns [%s]", username, requestedNS) if requestedNS == "" { @@ -281,11 +276,11 @@ func GetNamespace(clientset kubernetes.Interface, username, requestedNS string) return requestedNS, fmt.Errorf("Error when determining whether user [%s] is allowed access to "+ "namespace [%s]: %s", username, requestedNS, err.Error()) } - if iAccess == false { + if !iAccess { errMsg := fmt.Sprintf("namespace [%s] is not part of the Operator installation", requestedNS) return requestedNS, errors.New(errMsg) } - if uAccess == false { + if !uAccess { errMsg := fmt.Sprintf("user [%s] is not allowed access to namespace [%s]", username, requestedNS) return requestedNS, errors.New(errMsg) } @@ -339,7 +334,6 @@ func Authn(perm string, w http.ResponseWriter, r *http.Request) (string, error) log.Debug("Authentication Success") return username, err - } func IsValidStorageName(name string) bool { @@ -375,7 +369,7 @@ func UserIsPermittedInNamespace(username, requestedNS string) (bool, bool, error } if iAccess { - //get the pgouser Secret for this username + // get the pgouser Secret for this username userSecretName := "pgouser-" + username userSecret, err := Clientset.CoreV1().Secrets(PgoNamespace).Get(ctx, userSecretName, metav1.GetOptions{}) if err != nil { @@ -408,7 +402,6 @@ func UserIsPermittedInNamespace(username, requestedNS string) (bool, bool, error func WriteTLSCert(certPath, keyPath string) error { ctx := context.TODO() pgoSecret, err := Clientset.CoreV1().Secrets(PgoNamespace).Get(ctx, PGOSecretName, metav1.GetOptions{}) - // if the TLS certificate secret is not found, attempt to generate one if err != nil { log.Infof("%s Secret NOT found in namespace %s", PGOSecretName, PgoNamespace) @@ -425,13 +418,13 @@ func WriteTLSCert(certPath, keyPath string) error { log.Infof("%s Secret found in namespace %s", PGOSecretName, PgoNamespace) log.Infof("cert key data len is %d", len(pgoSecret.Data[corev1.TLSCertKey])) - if err := ioutil.WriteFile(certPath, pgoSecret.Data[corev1.TLSCertKey], 0644); err != nil { + if err := ioutil.WriteFile(certPath, pgoSecret.Data[corev1.TLSCertKey], 0o600); err != nil { return err } log.Infof("private key data len is %d", len(pgoSecret.Data[corev1.TLSPrivateKeyKey])) - if err := ioutil.WriteFile(keyPath, pgoSecret.Data[corev1.TLSPrivateKeyKey], 0644); err != nil { + if err := ioutil.WriteFile(keyPath, pgoSecret.Data[corev1.TLSPrivateKeyKey], 0o600); err != nil { return err } @@ -444,7 +437,7 @@ func generateTLSCert(certPath, keyPath string) error { ctx := context.TODO() var err error - //generate private key + // generate private key var privateKey *rsa.PrivateKey privateKey, err = tlsutil.NewPrivateKey() if err != nil { @@ -481,15 +474,14 @@ func generateTLSCert(certPath, keyPath string) error { os.Exit(2) } - if err := ioutil.WriteFile(certPath, newSecret.Data[corev1.TLSCertKey], 0644); err != nil { + if err := ioutil.WriteFile(certPath, newSecret.Data[corev1.TLSCertKey], 0o600); err != nil { return err } - if err := ioutil.WriteFile(keyPath, newSecret.Data[corev1.TLSPrivateKeyKey], 0644); err != nil { + if err := ioutil.WriteFile(keyPath, newSecret.Data[corev1.TLSPrivateKeyKey], 0o600); err != nil { return err } return err - } // setNamespaceOperatingMode set the namespace operating mode for the Operator by calling the @@ -530,7 +522,6 @@ func setRandomPgouserPasswords() { // generate the password using the default password length generatedPassword, err := util.GeneratePassword(util.DefaultGeneratedPasswordLength) - if err != nil { log.Errorf("Could not generate password for pgouser secret %s for operator installation %s in "+ "namespace %s", secret.Name, InstallationName, PgoNamespace) @@ -539,7 +530,6 @@ func setRandomPgouserPasswords() { // create the password patch patch, err := kubeapi.NewMergePatch().Add("stringData", "password")(generatedPassword).Bytes() - if err != nil { log.Errorf("Could not generate password patch for pgouser secret %s for operator installation "+ "%s in namespace %s", secret.Name, InstallationName, PgoNamespace) diff --git a/internal/apiserver/scheduleservice/scheduleimpl.go b/internal/apiserver/scheduleservice/scheduleimpl.go index 7aa2a9e194..f8c70ae059 100644 --- a/internal/apiserver/scheduleservice/scheduleimpl.go +++ b/internal/apiserver/scheduleservice/scheduleimpl.go @@ -143,8 +143,8 @@ func CreateSchedule(request *msgs.CreateScheduleRequest, ns string) msgs.CreateS log.Debug("Making schedules") var schedules []*PgScheduleSpec - for _, cluster := range clusterList.Items { - + for i := range clusterList.Items { + cluster := &clusterList.Items[i] // check if the current cluster is not upgraded to the deployed // Operator version. If not, do not allow the command to complete if cluster.Annotations[config.ANNOTATION_IS_UPGRADED] == config.ANNOTATIONS_FALSE { @@ -154,10 +154,10 @@ func CreateSchedule(request *msgs.CreateScheduleRequest, ns string) msgs.CreateS } switch sr.Request.ScheduleType { case "pgbackrest": - schedule := sr.createBackRestSchedule(&cluster, ns) + schedule := sr.createBackRestSchedule(cluster, ns) schedules = append(schedules, schedule) case "policy": - schedule := sr.createPolicySchedule(&cluster, ns) + schedule := sr.createPolicySchedule(cluster, ns) schedules = append(schedules, schedule) default: sr.Response.Status.Code = msgs.Error @@ -232,7 +232,7 @@ func DeleteSchedule(request *msgs.DeleteScheduleRequest, ns string) msgs.DeleteS if request.ScheduleName == "" && request.ClusterName == "" && request.Selector == "" { sr.Status.Code = msgs.Error - sr.Status.Msg = fmt.Sprintf("Cluster name, schedule name or selector must be provided") + sr.Status.Msg = "Cluster name, schedule name or selector must be provided" return *sr } @@ -280,7 +280,7 @@ func ShowSchedule(request *msgs.ShowScheduleRequest, ns string) msgs.ShowSchedul if request.ScheduleName == "" && request.ClusterName == "" && request.Selector == "" { sr.Status.Code = msgs.Error - sr.Status.Msg = fmt.Sprintf("Cluster name, schedule name or selector must be provided") + sr.Status.Msg = "Cluster name, schedule name or selector must be provided" return *sr } diff --git a/internal/apiserver/scheduleservice/scheduleservice.go b/internal/apiserver/scheduleservice/scheduleservice.go index b88fa16d7e..3ac9205a59 100644 --- a/internal/apiserver/scheduleservice/scheduleservice.go +++ b/internal/apiserver/scheduleservice/scheduleservice.go @@ -98,12 +98,12 @@ func CreateScheduleHandler(w http.ResponseWriter, r *http.Request) { }, Results: make([]string, 0), } - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp := CreateSchedule(&request, ns) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } func DeleteScheduleHandler(w http.ResponseWriter, r *http.Request) { @@ -150,13 +150,13 @@ func DeleteScheduleHandler(w http.ResponseWriter, r *http.Request) { }, Results: make([]string, 0), } - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp := DeleteSchedule(&request, ns) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } func ShowScheduleHandler(w http.ResponseWriter, r *http.Request) { @@ -204,10 +204,10 @@ func ShowScheduleHandler(w http.ResponseWriter, r *http.Request) { Results: make([]string, 0), } - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp := ShowSchedule(&request, ns) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } diff --git a/internal/apiserver/statusservice/statusimpl.go b/internal/apiserver/statusservice/statusimpl.go index 958da66604..7cc81278b0 100644 --- a/internal/apiserver/statusservice/statusimpl.go +++ b/internal/apiserver/statusservice/statusimpl.go @@ -46,7 +46,7 @@ func Status(ns string) msgs.StatusResponse { func getNumClaims(ns string) int { ctx := context.TODO() - //count number of PVCs with pgremove=true + // count number of PVCs with pgremove=true pvcs, err := apiserver.Clientset. CoreV1().PersistentVolumeClaims(ns). List(ctx, metav1.ListOptions{LabelSelector: config.LABEL_PGREMOVE}) @@ -59,7 +59,7 @@ func getNumClaims(ns string) int { func getNumDatabases(ns string) int { ctx := context.TODO() - //count number of Deployments with pg-cluster + // count number of Deployments with pg-cluster deps, err := apiserver.Clientset. AppsV1().Deployments(ns). List(ctx, metav1.ListOptions{LabelSelector: config.LABEL_PG_CLUSTER}) @@ -72,7 +72,7 @@ func getNumDatabases(ns string) int { func getVolumeCap(ns string) string { ctx := context.TODO() - //sum all PVCs storage capacity + // sum all PVCs storage capacity pvcs, err := apiserver.Clientset. CoreV1().PersistentVolumeClaims(ns). List(ctx, metav1.ListOptions{LabelSelector: config.LABEL_PGREMOVE}) @@ -83,18 +83,18 @@ func getVolumeCap(ns string) string { var capTotal int64 capTotal = 0 - for _, p := range pvcs.Items { - capTotal = capTotal + getClaimCapacity(&p) + for i := range pvcs.Items { + capTotal = capTotal + getClaimCapacity(&pvcs.Items[i]) } q := resource.NewQuantity(capTotal, resource.BinarySI) - //log.Infof("capTotal string is %s\n", q.String()) + // log.Infof("capTotal string is %s\n", q.String()) return q.String() } func getDBTags(ns string) map[string]int { ctx := context.TODO() results := make(map[string]int) - //count all pods with pg-cluster, sum by image tag value + // count all pods with pg-cluster, sum by image tag value pods, err := apiserver.Clientset.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{LabelSelector: config.LABEL_PG_CLUSTER}) if err != nil { log.Error(err) @@ -111,7 +111,7 @@ func getDBTags(ns string) map[string]int { func getNotReady(ns string) []string { ctx := context.TODO() - //show all database pods for each pgcluster that are not yet running + // show all database pods for each pgcluster that are not yet running agg := make([]string, 0) clusterList, err := apiserver.Clientset.CrunchydataV1().Pgclusters(ns).List(ctx, metav1.ListOptions{}) if err != nil { @@ -152,7 +152,6 @@ func getClaimCapacity(pvc *v1.PersistentVolumeClaim) int64 { diskSizeInt64, _ := diskSize.AsInt64() return diskSizeInt64 - } func getLabels(ns string) []msgs.KeyValue { @@ -168,7 +167,6 @@ func getLabels(ns string) []msgs.KeyValue { } for _, dep := range deps.Items { - for k, v := range dep.ObjectMeta.Labels { lv := k + "=" + v if results[lv] == 0 { @@ -177,7 +175,6 @@ func getLabels(ns string) []msgs.KeyValue { results[lv] = results[lv] + 1 } } - } for k, v := range results { @@ -189,5 +186,4 @@ func getLabels(ns string) []msgs.KeyValue { }) return ss - } diff --git a/internal/apiserver/statusservice/statusservice.go b/internal/apiserver/statusservice/statusservice.go index ecab0047c2..3adecd9156 100644 --- a/internal/apiserver/statusservice/statusservice.go +++ b/internal/apiserver/statusservice/statusservice.go @@ -17,11 +17,12 @@ limitations under the License. import ( "encoding/json" + "net/http" + "github.com/crunchydata/postgres-operator/internal/apiserver" msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" //"github.com/gorilla/mux" - "net/http" ) // StatusHandler ... @@ -71,7 +72,7 @@ func StatusHandler(w http.ResponseWriter, r *http.Request) { if clientVersion != msgs.PGO_VERSION { resp = msgs.StatusResponse{} resp.Status = msgs.Status{Code: msgs.Error, Msg: apiserver.VERSION_MISMATCH_ERROR} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } @@ -79,11 +80,11 @@ func StatusHandler(w http.ResponseWriter, r *http.Request) { if err != nil { resp = msgs.StatusResponse{} resp.Status = msgs.Status{Code: msgs.Error, Msg: err.Error()} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = Status(ns) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } diff --git a/internal/apiserver/upgradeservice/upgradeimpl.go b/internal/apiserver/upgradeservice/upgradeimpl.go index 69e3a40927..66d6a806b0 100644 --- a/internal/apiserver/upgradeservice/upgradeimpl.go +++ b/internal/apiserver/upgradeservice/upgradeimpl.go @@ -230,14 +230,12 @@ func supportedOperatorVersion(version string) bool { // If none of the above is true, the upgrade can continue return true - } // upgradeTagValid compares and validates the PostgreSQL version values stored // in the image tag of the existing pgcluster CR against the values set in the // Postgres Operator's configuration func upgradeTagValid(upgradeFrom, upgradeTo string) bool { - log.Debugf("Validating upgrade from %s to %s", upgradeFrom, upgradeTo) versionRegex := regexp.MustCompile(`-(\d+)\.(\d+)(\.\d+)?-`) @@ -280,5 +278,4 @@ func upgradeTagValid(upgradeFrom, upgradeTo string) bool { // if none of the above conditions are met, a two digit Major version upgrade is likely being // attempted, or a tag value or general error occurred, so we cannot continue return false - } diff --git a/internal/apiserver/upgradeservice/upgradeservice.go b/internal/apiserver/upgradeservice/upgradeservice.go index dee9c68dc2..b058345e6a 100644 --- a/internal/apiserver/upgradeservice/upgradeservice.go +++ b/internal/apiserver/upgradeservice/upgradeservice.go @@ -71,17 +71,17 @@ func CreateUpgradeHandler(w http.ResponseWriter, r *http.Request) { if request.ClientVersion != msgs.PGO_VERSION { resp.Status = msgs.Status{Code: msgs.Error, Msg: apiserver.VERSION_MISMATCH_ERROR} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } ns, err = apiserver.GetNamespace(apiserver.Clientset, username, request.Namespace) if err != nil { resp.Status = msgs.Status{Code: msgs.Error, Msg: err.Error()} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = CreateUpgrade(&request, ns, username) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } diff --git a/internal/apiserver/userservice/userimpl.go b/internal/apiserver/userservice/userimpl.go index 39e0396184..cc63850ba2 100644 --- a/internal/apiserver/userservice/userimpl.go +++ b/internal/apiserver/userservice/userimpl.go @@ -104,10 +104,8 @@ const ( sqlDelimiter = "|" ) -var ( - // sqlCommand is the command that needs to be executed for running SQL - sqlCommand = []string{"psql", "-A", "-t"} -) +// sqlCommand is the command that needs to be executed for running SQL +var sqlCommand = []string{"psql", "-A", "-t"} // CreatueUser allows one to create a PostgreSQL user in one of more PostgreSQL // clusters, and provides the abilit to do the following: @@ -138,7 +136,6 @@ func CreateUser(request *msgs.CreateUserRequest, pgouser string) msgs.CreateUser // try to get a list of clusters. if there is an error, return clusterList, err := getClusterList(request.Namespace, request.Clusters, request.Selector, request.AllFlag) - if err != nil { response.Status.Code = msgs.Error response.Status.Msg = err.Error() @@ -159,7 +156,6 @@ func CreateUser(request *msgs.CreateUserRequest, pgouser string) msgs.CreateUser // determine if the user passed in a valid password type passwordType, err := msgs.GetPasswordType(request.PasswordType) - if err != nil { response.Status.Code = msgs.Error response.Status.Msg = err.Error() @@ -182,7 +178,8 @@ func CreateUser(request *msgs.CreateUserRequest, pgouser string) msgs.CreateUser } // iterate through each cluster and add the new PostgreSQL role to each pod - for _, cluster := range clusterList.Items { + for i := range clusterList.Items { + cluster := &clusterList.Items[i] result := msgs.UserResponseDetail{ ClusterName: cluster.Spec.ClusterName, Username: request.Username, @@ -192,8 +189,7 @@ func CreateUser(request *msgs.CreateUserRequest, pgouser string) msgs.CreateUser log.Debugf("creating user [%s] on cluster [%s]", result.Username, cluster.Spec.ClusterName) // first, find the primary Pod - pod, err := util.GetPrimaryPod(apiserver.Clientset, &cluster) - + pod, err := util.GetPrimaryPod(apiserver.Clientset, cluster) // if the primary Pod cannot be found, we're going to continue on for the // other clusters, but provide some sort of error message in the response if err != nil { @@ -226,7 +222,6 @@ func CreateUser(request *msgs.CreateUserRequest, pgouser string) msgs.CreateUser // Set the password. We want a password to be generated if the user did not // set a password _, password, hashedPassword, err := generatePassword(result.Username, request.Password, passwordType, true, request.PasswordLength) - // on the off-chance there is an error, record it and continue if err != nil { log.Error(err) @@ -268,7 +263,7 @@ func CreateUser(request *msgs.CreateUserRequest, pgouser string) msgs.CreateUser } // if a pgAdmin deployment exists, attempt to add the user to it - if err := updatePgAdmin(&cluster, result.Username, result.Password); err != nil { + if err := updatePgAdmin(cluster, result.Username, result.Password); err != nil { log.Error(err) result.Error = true result.ErrorMessage = err.Error() @@ -306,7 +301,6 @@ func DeleteUser(request *msgs.DeleteUserRequest, pgouser string) msgs.DeleteUser // try to get a list of clusters. if there is an error, return clusterList, err := getClusterList(request.Namespace, request.Clusters, request.Selector, request.AllFlag) - if err != nil { response.Status.Code = msgs.Error response.Status.Msg = err.Error() @@ -315,7 +309,8 @@ func DeleteUser(request *msgs.DeleteUserRequest, pgouser string) msgs.DeleteUser // iterate through each cluster and try to delete the user! loop: - for _, cluster := range clusterList.Items { + for i := range clusterList.Items { + cluster := clusterList.Items[i] result := msgs.UserResponseDetail{ ClusterName: cluster.Spec.ClusterName, Username: request.Username, @@ -325,7 +320,6 @@ loop: // first, find the primary Pod pod, err := util.GetPrimaryPod(apiserver.Clientset, &cluster) - // if the primary Pod cannot be found, we're going to continue on for the // other clusters, but provide some sort of error message in the response if err != nil { @@ -341,7 +335,6 @@ loop: // first, get a list of all the databases in the cluster. We will need to // go through each database and drop any object that the user owns output, err := executeSQL(pod, cluster.Spec.Port, sqlFindDatabases, []string{}) - // if there is an error, record it and move on as we cannot actually deleted // the user if err != nil { @@ -452,7 +445,6 @@ func ShowUser(request *msgs.ShowUserRequest) msgs.ShowUserResponse { // them. If if this returns an error, exit here clusterList, err := getClusterList(request.Namespace, request.Clusters, request.Selector, request.AllFlag) - if err != nil { response.Status.Code = msgs.Error response.Status.Msg = err.Error() @@ -471,10 +463,10 @@ func ShowUser(request *msgs.ShowUserRequest) msgs.ShowUserResponse { } // iterate through each cluster and look up information about each user - for _, cluster := range clusterList.Items { + for i := range clusterList.Items { + cluster := clusterList.Items[i] // first, find the primary Pod pod, err := util.GetPrimaryPod(apiserver.Clientset, &cluster) - // if the primary Pod cannot be found, we're going to continue on for the // other clusters, but provide some sort of error message in the response if err != nil { @@ -503,7 +495,6 @@ func ShowUser(request *msgs.ShowUserRequest) msgs.ShowUserResponse { // great, now we can perform the user lookup output, err := executeSQL(pod, cluster.Spec.Port, sql, []string{}) - // if there is an error, record it and move on to the next cluster if err != nil { log.Error(err) @@ -627,7 +618,6 @@ func UpdateUser(request *msgs.UpdateUserRequest, pgouser string) msgs.UpdateUser // try to get a list of clusters. if there is an error, return clusterList, err := getClusterList(request.Namespace, request.Clusters, request.Selector, request.AllFlag) - if err != nil { response.Status.Code = msgs.Error response.Status.Msg = err.Error() @@ -644,20 +634,21 @@ func UpdateUser(request *msgs.UpdateUserRequest, pgouser string) msgs.UpdateUser return response } - for _, cluster := range clusterList.Items { + for i := range clusterList.Items { var result msgs.UserResponseDetail + cluster := &clusterList.Items[i] // determine which update user actions needs to be performed switch { // determine if any passwords expiring in X days should be updated // it returns a slice of results, which are then append to the list case request.Expired > 0: - results := rotateExpiredPasswords(request, &cluster) + results := rotateExpiredPasswords(request, cluster) response.Results = append(response.Results, results...) // otherwise, perform a regular "update user" request which covers all the // other "regular" cases. It returns a result, which is append to the list default: - result = updateUser(request, &cluster) + result = updateUser(request, cluster) response.Results = append(response.Results, result) } } @@ -674,7 +665,6 @@ func deleteUserSecret(cluster crv1.Pgcluster, username string) { secretName := fmt.Sprintf(util.UserSecretFormat, cluster.Spec.ClusterName, username) err := apiserver.Clientset.CoreV1().Secrets(cluster.Spec.Namespace). Delete(ctx, secretName, metav1.DeleteOptions{}) - if err != nil { log.Error(err) } @@ -746,7 +736,6 @@ func generatePassword(username, password string, passwordType pgpassword.Passwor // generate the password generatedPassword, err := util.GeneratePassword(passwordLength) - // if there is an error, return if err != nil { return false, "", "", err @@ -757,13 +746,11 @@ func generatePassword(username, password string, passwordType pgpassword.Passwor // finally, hash the password postgresPassword, err := pgpassword.NewPostgresPassword(passwordType, username, password) - if err != nil { return false, "", "", err } hashedPassword, err := postgresPassword.Build() - if err != nil { return false, "", "", err } @@ -830,7 +817,6 @@ func getClusterList(namespace string, clusterNames []string, selector string, al // of arguments...or both. First, start with the selector if selector != "" { cl, err := apiserver.Clientset.CrunchydataV1().Pgclusters(namespace).List(ctx, metav1.ListOptions{LabelSelector: selector}) - // if there is an error, return here with an empty cluster list if err != nil { return crv1.PgclusterList{}, err @@ -841,7 +827,6 @@ func getClusterList(namespace string, clusterNames []string, selector string, al // now try to get clusters based specific cluster names for _, clusterName := range clusterNames { cluster, err := apiserver.Clientset.CrunchydataV1().Pgclusters(namespace).Get(ctx, clusterName, metav1.GetOptions{}) - // if there is an error, capture it here and return here with an empty list if err != nil { return crv1.PgclusterList{}, err @@ -876,7 +861,6 @@ func rotateExpiredPasswords(request *msgs.UpdateUserRequest, cluster *crv1.Pgclu // first, find the primary Pod. If we can't do that, no rense in continuing pod, err := util.GetPrimaryPod(apiserver.Clientset, cluster) - if err != nil { result := msgs.UserResponseDetail{ ClusterName: cluster.Spec.ClusterName, @@ -902,7 +886,6 @@ func rotateExpiredPasswords(request *msgs.UpdateUserRequest, cluster *crv1.Pgclu // alright, time to find if there are any expired accounts. If this errors, // then we will abort here output, err := executeSQL(pod, cluster.Spec.Port, sql, []string{}) - if err != nil { result := msgs.UserResponseDetail{ ClusterName: cluster.Spec.ClusterName, @@ -972,7 +955,6 @@ func rotateExpiredPasswords(request *msgs.UpdateUserRequest, cluster *crv1.Pgclu // length of the password, or passed in a password to rotate (though that // is not advised...). This forced the password to change _, password, hashedPassword, err := generatePassword(result.Username, request.Password, passwordType, true, request.PasswordLength) - // on the off-chance there's an error in generating the password, record it // and continue if err != nil { @@ -1016,7 +998,6 @@ func updatePgAdmin(cluster *crv1.Pgcluster, username, password string) error { // Sync user to pgAdmin, if enabled qr, err := pgadmin.GetPgAdminQueryRunner(apiserver.Clientset, apiserver.RESTConfig, cluster) - // if there is an error, return as such if err != nil { return err @@ -1073,7 +1054,6 @@ func updateUser(request *msgs.UpdateUserRequest, cluster *crv1.Pgcluster) msgs.U // first, find the primary Pod pod, err := util.GetPrimaryPod(apiserver.Clientset, cluster) - // if the primary Pod cannot be found, we're going to continue on for the // other clusters, but provide some sort of error message in the response if err != nil { @@ -1104,7 +1084,6 @@ func updateUser(request *msgs.UpdateUserRequest, cluster *crv1.Pgcluster) msgs.U passwordType, _ := msgs.GetPasswordType(request.PasswordType) isChanged, password, hashedPassword, err := generatePassword(result.Username, request.Password, passwordType, request.RotatePassword, request.PasswordLength) - // in the off-chance there is an error generating the password, record it // and return if err != nil { @@ -1171,6 +1150,7 @@ func updateUser(request *msgs.UpdateUserRequest, cluster *crv1.Pgcluster) msgs.U sql = fmt.Sprintf("%s %s", sql, sqlEnableLoginClause) case msgs.UpdateUserLoginDisable: sql = fmt.Sprintf("%s %s", sql, sqlDisableLoginClause) + case msgs.UpdateUserLoginDoNothing: // this is never reached -- no-op } // execute the SQL! if there is an error, return the results diff --git a/internal/apiserver/userservice/userimpl_test.go b/internal/apiserver/userservice/userimpl_test.go index 71d3aa5fcf..f49d171ff4 100644 --- a/internal/apiserver/userservice/userimpl_test.go +++ b/internal/apiserver/userservice/userimpl_test.go @@ -30,9 +30,7 @@ func TestGeneratePassword(t *testing.T) { generatedPasswordLength := 32 t.Run("no changes", func(t *testing.T) { - changed, _, _, err := generatePassword(username, password, passwordType, generateNewPassword, generatedPasswordLength) - if err != nil { t.Error(err) return @@ -48,7 +46,6 @@ func TestGeneratePassword(t *testing.T) { t.Run("valid", func(t *testing.T) { changed, newPassword, _, err := generatePassword(username, password, passwordType, generateNewPassword, generatedPasswordLength) - if err != nil { t.Error(err) return @@ -66,7 +63,6 @@ func TestGeneratePassword(t *testing.T) { t.Run("does not override custom password", func(t *testing.T) { password := "custom" changed, newPassword, _, err := generatePassword(username, password, passwordType, generateNewPassword, generatedPasswordLength) - if err != nil { t.Error(err) return @@ -84,7 +80,6 @@ func TestGeneratePassword(t *testing.T) { t.Run("password length can be adjusted", func(t *testing.T) { generatedPasswordLength := 16 changed, newPassword, _, err := generatePassword(username, password, passwordType, generateNewPassword, generatedPasswordLength) - if err != nil { t.Error(err) return @@ -102,7 +97,6 @@ func TestGeneratePassword(t *testing.T) { t.Run("should be nonzero length", func(t *testing.T) { generatedPasswordLength := 0 changed, newPassword, _, err := generatePassword(username, password, passwordType, generateNewPassword, generatedPasswordLength) - if err != nil { t.Error(err) return @@ -125,7 +119,6 @@ func TestGeneratePassword(t *testing.T) { t.Run("md5", func(t *testing.T) { changed, _, hashedPassword, err := generatePassword(username, password, passwordType, generateNewPassword, generatedPasswordLength) - if err != nil { t.Error(err) return @@ -144,7 +137,6 @@ func TestGeneratePassword(t *testing.T) { passwordType := pgpassword.SCRAM changed, _, hashedPassword, err := generatePassword(username, password, passwordType, generateNewPassword, generatedPasswordLength) - if err != nil { t.Error(err) return @@ -159,5 +151,4 @@ func TestGeneratePassword(t *testing.T) { } }) }) - } diff --git a/internal/apiserver/userservice/userservice.go b/internal/apiserver/userservice/userservice.go index 83994c90fa..94a9be299b 100644 --- a/internal/apiserver/userservice/userservice.go +++ b/internal/apiserver/userservice/userservice.go @@ -17,10 +17,11 @@ limitations under the License. import ( "encoding/json" + "net/http" + "github.com/crunchydata/postgres-operator/internal/apiserver" msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" - "net/http" ) // UserHandler provides a means to update a PostgreSQL user @@ -52,7 +53,7 @@ func UpdateUserHandler(w http.ResponseWriter, r *http.Request) { username, err := apiserver.Authn(apiserver.UPDATE_USER_PERM, w, r) if err != nil { resp.Status = msgs.Status{Code: msgs.Error, Msg: apiserver.VERSION_MISMATCH_ERROR} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } @@ -62,20 +63,20 @@ func UpdateUserHandler(w http.ResponseWriter, r *http.Request) { if request.ClientVersion != msgs.PGO_VERSION { resp.Status = msgs.Status{Code: msgs.Error, Msg: apiserver.VERSION_MISMATCH_ERROR} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } _, err = apiserver.GetNamespace(apiserver.Clientset, username, request.Namespace) if err != nil { resp.Status = msgs.Status{Code: msgs.Error, Msg: err.Error()} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = UpdateUser(&request, username) - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } // CreateUserHandler ... @@ -117,20 +118,19 @@ func CreateUserHandler(w http.ResponseWriter, r *http.Request) { if err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } if request.ClientVersion != msgs.PGO_VERSION { resp.Status.Code = msgs.Error resp.Status.Msg = apiserver.VERSION_MISMATCH_ERROR - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = CreateUser(&request, username) - json.NewEncoder(w).Encode(resp) - + _ = json.NewEncoder(w).Encode(resp) } // DeleteUserHandler ... @@ -163,7 +163,7 @@ func DeleteUserHandler(w http.ResponseWriter, r *http.Request) { if request.ClientVersion != msgs.PGO_VERSION { resp.Status.Code = msgs.Error resp.Status.Msg = apiserver.VERSION_MISMATCH_ERROR - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } @@ -182,13 +182,12 @@ func DeleteUserHandler(w http.ResponseWriter, r *http.Request) { if err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = DeleteUser(&request, pgouser) - json.NewEncoder(w).Encode(resp) - + _ = json.NewEncoder(w).Encode(resp) } // ShowUserHandler allows one to display information about PostgreSQL uesrs that @@ -237,18 +236,17 @@ func ShowUserHandler(w http.ResponseWriter, r *http.Request) { resp := msgs.ShowUserResponse{} if request.ClientVersion != msgs.PGO_VERSION { resp.Status = msgs.Status{Code: msgs.Error, Msg: apiserver.VERSION_MISMATCH_ERROR} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } _, err = apiserver.GetNamespace(apiserver.Clientset, username, request.Namespace) if err != nil { resp.Status = msgs.Status{Code: msgs.Error, Msg: err.Error()} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } resp = ShowUser(&request) - json.NewEncoder(w).Encode(resp) - + _ = json.NewEncoder(w).Encode(resp) } diff --git a/internal/apiserver/versionservice/versionservice.go b/internal/apiserver/versionservice/versionservice.go index 49735dff1a..bcb2adc7b0 100644 --- a/internal/apiserver/versionservice/versionservice.go +++ b/internal/apiserver/versionservice/versionservice.go @@ -17,9 +17,10 @@ limitations under the License. import ( "encoding/json" + "net/http" + "github.com/crunchydata/postgres-operator/internal/apiserver" log "github.com/sirupsen/logrus" - "net/http" ) // VersionHandler ... @@ -50,7 +51,7 @@ func VersionHandler(w http.ResponseWriter, r *http.Request) { resp := Version() - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } // HealthHandler ... @@ -71,7 +72,7 @@ func HealthHandler(w http.ResponseWriter, r *http.Request) { resp := Health() - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } // HealthyHandler follows the health endpoint convention of HTTP/200 and @@ -88,5 +89,5 @@ func HealthyHandler(w http.ResponseWriter, r *http.Request) { // '200': // description: "Healthy: server is responding as expected" w.WriteHeader(http.StatusOK) - w.Write([]byte("ok")) + _, _ = w.Write([]byte("ok")) } diff --git a/internal/apiserver/workflowservice/workflowimpl.go b/internal/apiserver/workflowservice/workflowimpl.go index e07ff79d59..3f46b2a26c 100644 --- a/internal/apiserver/workflowservice/workflowimpl.go +++ b/internal/apiserver/workflowservice/workflowimpl.go @@ -34,7 +34,7 @@ func ShowWorkflow(id, ns string) (msgs.ShowWorkflowDetail, error) { log.Debugf("ShowWorkflow called with id %s", id) detail := msgs.ShowWorkflowDetail{} - //get the pgtask for this workflow + // get the pgtask for this workflow selector := crv1.PgtaskWorkflowID + "=" + id @@ -53,5 +53,4 @@ func ShowWorkflow(id, ns string) (msgs.ShowWorkflowDetail, error) { detail.Parameters = t.Spec.Parameters return detail, err - } diff --git a/internal/apiserver/workflowservice/workflowservice.go b/internal/apiserver/workflowservice/workflowservice.go index 81aea9ff98..35adb2b0a4 100644 --- a/internal/apiserver/workflowservice/workflowservice.go +++ b/internal/apiserver/workflowservice/workflowservice.go @@ -17,11 +17,12 @@ limitations under the License. import ( "encoding/json" + "net/http" + "github.com/crunchydata/postgres-operator/internal/apiserver" msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" - "net/http" ) // ShowWorkflowHandler ... @@ -79,7 +80,7 @@ func ShowWorkflowHandler(w http.ResponseWriter, r *http.Request) { if clientVersion != msgs.PGO_VERSION { resp.Status.Code = msgs.Error resp.Status.Msg = apiserver.VERSION_MISMATCH_ERROR - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } @@ -87,7 +88,7 @@ func ShowWorkflowHandler(w http.ResponseWriter, r *http.Request) { if err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) return } @@ -97,5 +98,5 @@ func ShowWorkflowHandler(w http.ResponseWriter, r *http.Request) { resp.Status.Msg = err.Error() } - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) } diff --git a/internal/config/labels.go b/internal/config/labels.go index 4b540a5227..d6d851eb52 100644 --- a/internal/config/labels.go +++ b/internal/config/labels.go @@ -16,149 +16,184 @@ package config */ // resource labels used by the operator -const LABEL_NAME = "name" -const LABEL_SELECTOR = "selector" -const LABEL_OPERATOR = "postgres-operator" -const LABEL_PG_CLUSTER = "pg-cluster" -const LABEL_PG_CLUSTER_IDENTIFIER = "pg-cluster-id" -const LABEL_PG_DATABASE = "pgo-pg-database" +const ( + LABEL_NAME = "name" + LABEL_SELECTOR = "selector" + LABEL_OPERATOR = "postgres-operator" + LABEL_PG_CLUSTER = "pg-cluster" + LABEL_PG_CLUSTER_IDENTIFIER = "pg-cluster-id" + LABEL_PG_DATABASE = "pgo-pg-database" +) const LABEL_PGTASK = "pg-task" -const LABEL_AUTOFAIL = "autofail" -const LABEL_FAILOVER = "failover" -const LABEL_RESTART = "restart" - -const LABEL_TARGET = "target" -const LABEL_RMDATA = "pgrmdata" - -const LABEL_PGPOLICY = "pgpolicy" -const LABEL_INGEST = "ingest" -const LABEL_PGREMOVE = "pgremove" -const LABEL_PVCNAME = "pvcname" -const LABEL_EXPORTER = "crunchy-postgres-exporter" -const LABEL_ARCHIVE = "archive" -const LABEL_ARCHIVE_TIMEOUT = "archive-timeout" -const LABEL_CUSTOM_CONFIG = "custom-config" -const LABEL_NODE_LABEL_KEY = "NodeLabelKey" -const LABEL_NODE_LABEL_VALUE = "NodeLabelValue" -const LABEL_REPLICA_NAME = "replica-name" -const LABEL_CCP_IMAGE_TAG_KEY = "ccp-image-tag" -const LABEL_CCP_IMAGE_KEY = "ccp-image" -const LABEL_IMAGE_PREFIX = "image-prefix" -const LABEL_SERVICE_TYPE = "service-type" -const LABEL_POD_ANTI_AFFINITY = "pg-pod-anti-affinity" -const LABEL_SYNC_REPLICATION = "sync-replication" - -const LABEL_REPLICA_COUNT = "replica-count" -const LABEL_STORAGE_CONFIG = "storage-config" -const LABEL_NODE_LABEL = "node-label" -const LABEL_VERSION = "version" -const LABEL_PGO_VERSION = "pgo-version" -const LABEL_DELETE_DATA = "delete-data" -const LABEL_DELETE_DATA_STARTED = "delete-data-started" -const LABEL_DELETE_BACKUPS = "delete-backups" -const LABEL_IS_REPLICA = "is-replica" -const LABEL_IS_BACKUP = "is-backup" -const LABEL_STARTUP = "startup" -const LABEL_SHUTDOWN = "shutdown" +const ( + LABEL_AUTOFAIL = "autofail" + LABEL_FAILOVER = "failover" + LABEL_RESTART = "restart" +) + +const ( + LABEL_TARGET = "target" + LABEL_RMDATA = "pgrmdata" +) + +const ( + LABEL_PGPOLICY = "pgpolicy" + LABEL_INGEST = "ingest" + LABEL_PGREMOVE = "pgremove" + LABEL_PVCNAME = "pvcname" + LABEL_EXPORTER = "crunchy-postgres-exporter" + LABEL_ARCHIVE = "archive" + LABEL_ARCHIVE_TIMEOUT = "archive-timeout" + LABEL_CUSTOM_CONFIG = "custom-config" + LABEL_NODE_LABEL_KEY = "NodeLabelKey" + LABEL_NODE_LABEL_VALUE = "NodeLabelValue" + LABEL_REPLICA_NAME = "replica-name" + LABEL_CCP_IMAGE_TAG_KEY = "ccp-image-tag" + LABEL_CCP_IMAGE_KEY = "ccp-image" + LABEL_IMAGE_PREFIX = "image-prefix" + LABEL_SERVICE_TYPE = "service-type" + LABEL_POD_ANTI_AFFINITY = "pg-pod-anti-affinity" + LABEL_SYNC_REPLICATION = "sync-replication" +) + +const ( + LABEL_REPLICA_COUNT = "replica-count" + LABEL_STORAGE_CONFIG = "storage-config" + LABEL_NODE_LABEL = "node-label" + LABEL_VERSION = "version" + LABEL_PGO_VERSION = "pgo-version" + LABEL_DELETE_DATA = "delete-data" + LABEL_DELETE_DATA_STARTED = "delete-data-started" + LABEL_DELETE_BACKUPS = "delete-backups" + LABEL_IS_REPLICA = "is-replica" + LABEL_IS_BACKUP = "is-backup" + LABEL_STARTUP = "startup" + LABEL_SHUTDOWN = "shutdown" +) // label for the pgcluster upgrade const LABEL_UPGRADE = "upgrade" -const LABEL_BACKREST = "pgo-backrest" -const LABEL_BACKREST_JOB = "pgo-backrest-job" -const LABEL_BACKREST_RESTORE = "pgo-backrest-restore" -const LABEL_CONTAINER_NAME = "containername" -const LABEL_POD_NAME = "podname" -const LABEL_BACKREST_REPO_SECRET = "backrest-repo-config" -const LABEL_BACKREST_COMMAND = "backrest-command" -const LABEL_BACKREST_RESTORE_FROM_CLUSTER = "backrest-restore-from-cluster" -const LABEL_BACKREST_RESTORE_OPTS = "backrest-restore-opts" -const LABEL_BACKREST_BACKUP_OPTS = "backrest-backup-opts" -const LABEL_BACKREST_OPTS = "backrest-opts" -const LABEL_BACKREST_PITR_TARGET = "backrest-pitr-target" -const LABEL_BACKREST_STORAGE_TYPE = "backrest-storage-type" -const LABEL_BACKREST_S3_VERIFY_TLS = "backrest-s3-verify-tls" -const LABEL_BADGER = "crunchy-pgbadger" -const LABEL_BADGER_CCPIMAGE = "crunchy-pgbadger" -const LABEL_BACKUP_TYPE_BACKREST = "pgbackrest" -const LABEL_BACKUP_TYPE_PGDUMP = "pgdump" - -const LABEL_PGDUMP_COMMAND = "pgdump" -const LABEL_PGDUMP_RESTORE = "pgdump-restore" -const LABEL_PGDUMP_OPTS = "pgdump-opts" -const LABEL_PGDUMP_HOST = "pgdump-host" -const LABEL_PGDUMP_DB = "pgdump-db" -const LABEL_PGDUMP_USER = "pgdump-user" -const LABEL_PGDUMP_PORT = "pgdump-port" -const LABEL_PGDUMP_ALL = "pgdump-all" -const LABEL_PGDUMP_PVC = "pgdump-pvc" - -const LABEL_RESTORE_TYPE_PGRESTORE = "pgrestore" -const LABEL_PGRESTORE_COMMAND = "pgrestore" -const LABEL_PGRESTORE_HOST = "pgrestore-host" -const LABEL_PGRESTORE_DB = "pgrestore-db" -const LABEL_PGRESTORE_USER = "pgrestore-user" -const LABEL_PGRESTORE_PORT = "pgrestore-port" -const LABEL_PGRESTORE_FROM_CLUSTER = "pgrestore-from-cluster" -const LABEL_PGRESTORE_FROM_PVC = "pgrestore-from-pvc" -const LABEL_PGRESTORE_OPTS = "pgrestore-opts" -const LABEL_PGRESTORE_PITR_TARGET = "pgrestore-pitr-target" - -const LABEL_DATA_ROOT = "data-root" -const LABEL_PVC_NAME = "pvc-name" -const LABEL_VOLUME_NAME = "volume-name" - -const LABEL_SESSION_ID = "sessionid" -const LABEL_USERNAME = "username" -const LABEL_ROLENAME = "rolename" -const LABEL_PASSWORD = "password" - -const LABEL_PGADMIN = "crunchy-pgadmin" -const LABEL_PGADMIN_TASK_ADD = "pgadmin-add" -const LABEL_PGADMIN_TASK_CLUSTER = "pgadmin-cluster" -const LABEL_PGADMIN_TASK_DELETE = "pgadmin-delete" +const ( + LABEL_BACKREST = "pgo-backrest" + LABEL_BACKREST_JOB = "pgo-backrest-job" + LABEL_BACKREST_RESTORE = "pgo-backrest-restore" + LABEL_CONTAINER_NAME = "containername" + LABEL_POD_NAME = "podname" + // #nosec: G101 + LABEL_BACKREST_REPO_SECRET = "backrest-repo-config" + LABEL_BACKREST_COMMAND = "backrest-command" + LABEL_BACKREST_RESTORE_FROM_CLUSTER = "backrest-restore-from-cluster" + LABEL_BACKREST_RESTORE_OPTS = "backrest-restore-opts" + LABEL_BACKREST_BACKUP_OPTS = "backrest-backup-opts" + LABEL_BACKREST_OPTS = "backrest-opts" + LABEL_BACKREST_PITR_TARGET = "backrest-pitr-target" + LABEL_BACKREST_STORAGE_TYPE = "backrest-storage-type" + LABEL_BACKREST_S3_VERIFY_TLS = "backrest-s3-verify-tls" + LABEL_BADGER = "crunchy-pgbadger" + LABEL_BADGER_CCPIMAGE = "crunchy-pgbadger" + LABEL_BACKUP_TYPE_BACKREST = "pgbackrest" + LABEL_BACKUP_TYPE_PGDUMP = "pgdump" +) + +const ( + LABEL_PGDUMP_COMMAND = "pgdump" + LABEL_PGDUMP_RESTORE = "pgdump-restore" + LABEL_PGDUMP_OPTS = "pgdump-opts" + LABEL_PGDUMP_HOST = "pgdump-host" + LABEL_PGDUMP_DB = "pgdump-db" + LABEL_PGDUMP_USER = "pgdump-user" + LABEL_PGDUMP_PORT = "pgdump-port" + LABEL_PGDUMP_ALL = "pgdump-all" + LABEL_PGDUMP_PVC = "pgdump-pvc" +) + +const ( + LABEL_RESTORE_TYPE_PGRESTORE = "pgrestore" + LABEL_PGRESTORE_COMMAND = "pgrestore" + LABEL_PGRESTORE_HOST = "pgrestore-host" + LABEL_PGRESTORE_DB = "pgrestore-db" + LABEL_PGRESTORE_USER = "pgrestore-user" + LABEL_PGRESTORE_PORT = "pgrestore-port" + LABEL_PGRESTORE_FROM_CLUSTER = "pgrestore-from-cluster" + LABEL_PGRESTORE_FROM_PVC = "pgrestore-from-pvc" + LABEL_PGRESTORE_OPTS = "pgrestore-opts" + LABEL_PGRESTORE_PITR_TARGET = "pgrestore-pitr-target" +) + +const ( + LABEL_DATA_ROOT = "data-root" + LABEL_PVC_NAME = "pvc-name" + LABEL_VOLUME_NAME = "volume-name" +) + +const ( + LABEL_SESSION_ID = "sessionid" + LABEL_USERNAME = "username" + LABEL_ROLENAME = "rolename" + LABEL_PASSWORD = "password" +) + +const ( + LABEL_PGADMIN = "crunchy-pgadmin" + LABEL_PGADMIN_TASK_ADD = "pgadmin-add" + LABEL_PGADMIN_TASK_CLUSTER = "pgadmin-cluster" + LABEL_PGADMIN_TASK_DELETE = "pgadmin-delete" +) const LABEL_PGBOUNCER = "crunchy-pgbouncer" -const LABEL_JOB_NAME = "job-name" -const LABEL_PGBACKREST_STANZA = "pgbackrest-stanza" -const LABEL_PGBACKREST_DB_PATH = "pgbackrest-db-path" -const LABEL_PGBACKREST_REPO_PATH = "pgbackrest-repo-path" -const LABEL_PGBACKREST_REPO_HOST = "pgbackrest-repo-host" +const ( + LABEL_JOB_NAME = "job-name" + LABEL_PGBACKREST_STANZA = "pgbackrest-stanza" + LABEL_PGBACKREST_DB_PATH = "pgbackrest-db-path" + LABEL_PGBACKREST_REPO_PATH = "pgbackrest-repo-path" + LABEL_PGBACKREST_REPO_HOST = "pgbackrest-repo-host" +) const LABEL_PGO_BACKREST_REPO = "pgo-backrest-repo" -const LABEL_DEPLOYMENT_NAME = "deployment-name" -const LABEL_SERVICE_NAME = "service-name" -const LABEL_CURRENT_PRIMARY = "current-primary" +const ( + LABEL_DEPLOYMENT_NAME = "deployment-name" + LABEL_SERVICE_NAME = "service-name" + LABEL_CURRENT_PRIMARY = "current-primary" +) const LABEL_CLAIM_NAME = "claimName" -const LABEL_PGO_PGOUSER = "pgo-pgouser" -const LABEL_PGO_PGOROLE = "pgo-pgorole" -const LABEL_PGOUSER = "pgouser" -const LABEL_WORKFLOW_ID = "workflowid" // NOTE: this now matches crv1.PgtaskWorkflowID - -const LABEL_TRUE = "true" -const LABEL_FALSE = "false" - -const LABEL_NAMESPACE = "namespace" -const LABEL_PGO_INSTALLATION_NAME = "pgo-installation-name" -const LABEL_VENDOR = "vendor" -const LABEL_CRUNCHY = "crunchydata" -const LABEL_PGO_CREATED_BY = "pgo-created-by" -const LABEL_PGO_UPDATED_BY = "pgo-updated-by" +const ( + LABEL_PGO_PGOUSER = "pgo-pgouser" + LABEL_PGO_PGOROLE = "pgo-pgorole" + LABEL_PGOUSER = "pgouser" + LABEL_WORKFLOW_ID = "workflowid" // NOTE: this now matches crv1.PgtaskWorkflowID +) + +const ( + LABEL_TRUE = "true" + LABEL_FALSE = "false" +) + +const ( + LABEL_NAMESPACE = "namespace" + LABEL_PGO_INSTALLATION_NAME = "pgo-installation-name" + LABEL_VENDOR = "vendor" + LABEL_CRUNCHY = "crunchydata" + LABEL_PGO_CREATED_BY = "pgo-created-by" + LABEL_PGO_UPDATED_BY = "pgo-updated-by" +) const LABEL_FAILOVER_STARTED = "failover-started" const GLOBAL_CUSTOM_CONFIGMAP = "pgo-custom-pg-config" -const LABEL_PGHA_SCOPE = "crunchy-pgha-scope" -const LABEL_PGHA_CONFIGMAP = "pgha-config" -const LABEL_PGHA_BACKUP_TYPE = "pgha-backup-type" -const LABEL_PGHA_ROLE = "role" -const LABEL_PGHA_ROLE_PRIMARY = "master" -const LABEL_PGHA_ROLE_REPLICA = "replica" -const LABEL_PGHA_BOOTSTRAP = "pgha-bootstrap" +const ( + LABEL_PGHA_SCOPE = "crunchy-pgha-scope" + LABEL_PGHA_CONFIGMAP = "pgha-config" + LABEL_PGHA_BACKUP_TYPE = "pgha-backup-type" + LABEL_PGHA_ROLE = "role" + LABEL_PGHA_ROLE_PRIMARY = "master" + LABEL_PGHA_ROLE_REPLICA = "replica" + LABEL_PGHA_BOOTSTRAP = "pgha-bootstrap" +) diff --git a/internal/config/pgoconfig.go b/internal/config/pgoconfig.go index bf7c6fb35d..c073d9c43f 100644 --- a/internal/config/pgoconfig.go +++ b/internal/config/pgoconfig.go @@ -38,8 +38,10 @@ import ( "sigs.k8s.io/yaml" ) -const CustomConfigMapName = "pgo-config" -const defaultConfigPath = "/default-pgo-config/" +const ( + CustomConfigMapName = "pgo-config" + defaultConfigPath = "/default-pgo-config/" +) var PgoDefaultServiceAccountTemplate *template.Template @@ -260,17 +262,21 @@ type PgoConfig struct { Storage map[string]StorageStruct } -const DEFAULT_SERVICE_TYPE = "ClusterIP" -const LOAD_BALANCER_SERVICE_TYPE = "LoadBalancer" -const NODEPORT_SERVICE_TYPE = "NodePort" -const CONFIG_PATH = "pgo.yaml" +const ( + DEFAULT_SERVICE_TYPE = "ClusterIP" + LOAD_BALANCER_SERVICE_TYPE = "LoadBalancer" + NODEPORT_SERVICE_TYPE = "NodePort" + CONFIG_PATH = "pgo.yaml" +) -const DEFAULT_BACKREST_PORT = 2022 -const DEFAULT_PGADMIN_PORT = "5050" -const DEFAULT_PGBADGER_PORT = "10000" -const DEFAULT_EXPORTER_PORT = "9187" -const DEFAULT_POSTGRES_PORT = "5432" -const DEFAULT_PATRONI_PORT = "8009" +const ( + DEFAULT_BACKREST_PORT = 2022 + DEFAULT_PGADMIN_PORT = "5050" + DEFAULT_PGBADGER_PORT = "10000" + DEFAULT_EXPORTER_PORT = "9187" + DEFAULT_POSTGRES_PORT = "5432" + DEFAULT_PATRONI_PORT = "8009" +) func (c *PgoConfig) Validate() error { var err error @@ -508,19 +514,16 @@ func (c *PgoConfig) GetStorageSpec(name string) (crv1.PgStorageSpec, error) { } return storage, err - } func (c *PgoConfig) GetConfig(clientset kubernetes.Interface, namespace string) error { - cMap, err := initialize(clientset, namespace) - if err != nil { log.Errorf("could not get ConfigMap: %s", err.Error()) return err } - //get the pgo.yaml config file + // get the pgo.yaml config file str := cMap.Data[CONFIG_PATH] if str == "" { return fmt.Errorf("could not get %s from ConfigMap", CONFIG_PATH) @@ -541,7 +544,7 @@ func (c *PgoConfig) GetConfig(clientset kubernetes.Interface, namespace string) c.CheckEnv() - //load up all the templates + // load up all the templates PgoDefaultServiceAccountTemplate, err = c.LoadTemplate(cMap, PGODefaultServiceAccountPath) if err != nil { return err @@ -729,7 +732,7 @@ func getOperatorConfigMap(clientset kubernetes.Interface, namespace string) (*v1 return clientset.CoreV1().ConfigMaps(namespace).Get(ctx, CustomConfigMapName, metav1.GetOptions{}) } -// initialize attemps to get the configuration ConfigMap based on a name. +// initialize attempts to get the configuration ConfigMap based on a name. // If the ConfigMap does not exist, a ConfigMap is created from the default // configuration path func initialize(clientset kubernetes.Interface, namespace string) (*v1.ConfigMap, error) { @@ -812,7 +815,6 @@ func (c *PgoConfig) LoadTemplate(cMap *v1.ConfigMap, path string) (*template.Tem // if we have a value for the templated file, return return template.Must(template.New(path).Parse(value)), nil - } // DefaultTemplate attempts to load a default configuration template file @@ -825,7 +827,6 @@ func (c *PgoConfig) DefaultTemplate(path string) (string, error) { // read in the file from the default path buf, err := ioutil.ReadFile(fullPath) - if err != nil { log.Errorf("error: could not read %s", fullPath) log.Error(err) diff --git a/internal/config/secrets.go b/internal/config/secrets.go index 2cc2b5ba1b..f518c813ba 100644 --- a/internal/config/secrets.go +++ b/internal/config/secrets.go @@ -15,4 +15,5 @@ package config limitations under the License. */ +// #nosec: G101 const SecretOperatorBackrestRepoConfig = "pgo-backrest-repo-config" diff --git a/internal/config/volumes.go b/internal/config/volumes.go index d21c2d6a4e..8723f9670a 100644 --- a/internal/config/volumes.go +++ b/internal/config/volumes.go @@ -22,8 +22,10 @@ import ( ) // volume configuration settings used by the PostgreSQL data directory and mount -const VOLUME_POSTGRESQL_DATA = "pgdata" -const VOLUME_POSTGRESQL_DATA_MOUNT_PATH = "/pgdata" +const ( + VOLUME_POSTGRESQL_DATA = "pgdata" + VOLUME_POSTGRESQL_DATA_MOUNT_PATH = "/pgdata" +) // PostgreSQLWALVolumeMount returns the VolumeMount for the PostgreSQL WAL directory. func PostgreSQLWALVolumeMount() core_v1.VolumeMount { @@ -36,12 +38,16 @@ func PostgreSQLWALPath(cluster string) string { } // volume configuration settings used by the pgBackRest repo mount -const VOLUME_PGBACKREST_REPO_NAME = "backrestrepo" -const VOLUME_PGBACKREST_REPO_MOUNT_PATH = "/backrestrepo" +const ( + VOLUME_PGBACKREST_REPO_NAME = "backrestrepo" + VOLUME_PGBACKREST_REPO_MOUNT_PATH = "/backrestrepo" +) // volume configuration settings used by the SSHD secret -const VOLUME_SSHD_NAME = "sshd" -const VOLUME_SSHD_MOUNT_PATH = "/sshd" +const ( + VOLUME_SSHD_NAME = "sshd" + VOLUME_SSHD_MOUNT_PATH = "/sshd" +) // volume configuration settings used by tablespaces diff --git a/internal/controller/configmap/configmapcontroller.go b/internal/controller/configmap/configmapcontroller.go index a7145ea6bf..a390075b67 100644 --- a/internal/controller/configmap/configmapcontroller.go +++ b/internal/controller/configmap/configmapcontroller.go @@ -48,7 +48,6 @@ type Controller struct { func NewConfigMapController(restConfig *rest.Config, clientset kubernetes.Interface, coreInformer coreinformers.ConfigMapInformer, pgoInformer pgoinformers.PgclusterInformer, workerCount int) (*Controller, error) { - controller := &Controller{ cmRESTConfig: restConfig, kubeclientset: clientset, @@ -77,7 +76,6 @@ func NewConfigMapController(restConfig *rest.Config, // function in order to read and process a message on the worker queue. Once the worker queue // is instructed to shutdown, a message is written to the done channel. func (c *Controller) RunWorker(stopCh <-chan struct{}, doneCh chan<- struct{}) { - go c.waitForShutdown(stopCh) for c.processNextWorkItem() { @@ -105,7 +103,6 @@ func (c *Controller) ShutdownWorker() { // so, the configMap resource is converted into a namespace/name string and is then added to the // work queue func (c *Controller) enqueueConfigMap(obj interface{}) { - configMap := obj.(*apiv1.ConfigMap) labels := configMap.GetObjectMeta().GetLabels() @@ -128,7 +125,6 @@ func (c *Controller) enqueueConfigMap(obj interface{}) { // processNextWorkItem will read a single work item off the work queue and processes it via // the ConfigMap sync handler func (c *Controller) processNextWorkItem() bool { - obj, shutdown := c.workqueue.Get() if shutdown { diff --git a/internal/controller/configmap/synchandler.go b/internal/controller/configmap/synchandler.go index 9309c0555c..b556f1561a 100644 --- a/internal/controller/configmap/synchandler.go +++ b/internal/controller/configmap/synchandler.go @@ -31,7 +31,6 @@ import ( // handleConfigMapSync is responsible for syncing a configMap resource that has obtained from // the ConfigMap controller's worker queue func (c *Controller) handleConfigMapSync(key string) error { - log.Debugf("ConfigMap Controller: handling a configmap sync for key %s", key) namespace, configMapName, err := cache.SplitMetaNamespaceKey(key) @@ -72,7 +71,7 @@ func (c *Controller) handleConfigMapSync(key string) error { return nil } - c.syncPGHAConfig(c.createPGHAConfigs(configMap, clusterName, + c.syncPGHAConfig(c.createPGHAConfigs(configMap, cluster.GetObjectMeta().GetLabels()[config.LABEL_PGHA_SCOPE])) return nil @@ -80,8 +79,7 @@ func (c *Controller) handleConfigMapSync(key string) error { // createConfigurerMap creates the configs needed to sync the PGHA configMap func (c *Controller) createPGHAConfigs(configMap *corev1.ConfigMap, - clusterName, clusterScope string) []cfg.Syncer { - + clusterScope string) []cfg.Syncer { var configSyncers []cfg.Syncer configSyncers = append(configSyncers, cfg.NewDCS(configMap, c.kubeclientset, clusterScope)) @@ -100,7 +98,6 @@ func (c *Controller) createPGHAConfigs(configMap *corev1.ConfigMap, // syncAllConfigs takes a map of configurers and runs their sync functions concurrently func (c *Controller) syncPGHAConfig(configSyncers []cfg.Syncer) { - var wg sync.WaitGroup for _, configSyncer := range configSyncers { diff --git a/internal/controller/controllerutil.go b/internal/controller/controllerutil.go index 7bbcd2b981..4b1f5da6ba 100644 --- a/internal/controller/controllerutil.go +++ b/internal/controller/controllerutil.go @@ -62,7 +62,8 @@ func InitializeReplicaCreation(clientset pgo.Interface, clusterName, log.Error(err) return err } - for _, pgreplica := range pgreplicaList.Items { + for i := range pgreplicaList.Items { + pgreplica := &pgreplicaList.Items[i] if pgreplica.Annotations == nil { pgreplica.Annotations = make(map[string]string) @@ -70,7 +71,7 @@ func InitializeReplicaCreation(clientset pgo.Interface, clusterName, pgreplica.Annotations[config.ANNOTATION_PGHA_BOOTSTRAP_REPLICA] = "true" - if _, err = clientset.CrunchydataV1().Pgreplicas(namespace).Update(ctx, &pgreplica, metav1.UpdateOptions{}); err != nil { + if _, err = clientset.CrunchydataV1().Pgreplicas(namespace).Update(ctx, pgreplica, metav1.UpdateOptions{}); err != nil { log.Error(err) return err } diff --git a/internal/controller/job/backresthandler.go b/internal/controller/job/backresthandler.go index cab5f3d068..40c10110ae 100644 --- a/internal/controller/job/backresthandler.go +++ b/internal/controller/job/backresthandler.go @@ -32,32 +32,29 @@ import ( ) // backrestUpdateHandler is responsible for handling updates to backrest jobs -func (c *Controller) handleBackrestUpdate(job *apiv1.Job) error { - +func (c *Controller) handleBackrestUpdate(job *apiv1.Job) { // return if job wasn't successful if !isJobSuccessful(job) { log.Debugf("jobController onUpdate job %s was unsuccessful and will be ignored", job.Name) - return nil + return } // return if job is being deleted if isJobInForegroundDeletion(job) { log.Debugf("jobController onUpdate job %s is being deleted and will be ignored", job.Name) - return nil + return } labels := job.GetObjectMeta().GetLabels() switch { case labels[config.LABEL_BACKREST_COMMAND] == "backup": - c.handleBackrestBackupUpdate(job) + _ = c.handleBackrestBackupUpdate(job) case labels[config.LABEL_BACKREST_COMMAND] == crv1.PgtaskBackrestStanzaCreate: - c.handleBackrestStanzaCreateUpdate(job) + _ = c.handleBackrestStanzaCreateUpdate(job) } - - return nil } // handleBackrestRestoreUpdate is responsible for handling updates to backrest backup jobs @@ -79,7 +76,7 @@ func (c *Controller) handleBackrestBackupUpdate(job *apiv1.Job) error { if err != nil { log.Errorf("error in patching pgtask %s: %s", job.ObjectMeta.SelfLink, err.Error()) } - publishBackupComplete(labels[config.LABEL_PG_CLUSTER], job.ObjectMeta.Labels[config.LABEL_PG_CLUSTER_IDENTIFIER], job.ObjectMeta.Labels[config.LABEL_PGOUSER], "pgbackrest", job.ObjectMeta.Namespace, "") + publishBackupComplete(labels[config.LABEL_PG_CLUSTER], job.ObjectMeta.Labels[config.LABEL_PGOUSER], "pgbackrest", job.ObjectMeta.Namespace, "") // If the completed backup was a cluster bootstrap backup, then mark the cluster as initialized // and initiate the creation of any replicas. Otherwise if the completed backup was taken as @@ -87,11 +84,11 @@ func (c *Controller) handleBackrestBackupUpdate(job *apiv1.Job) error { if labels[config.LABEL_PGHA_BACKUP_TYPE] == crv1.BackupTypeBootstrap { log.Debugf("jobController onUpdate initial backup complete") - controller.SetClusterInitializedStatus(c.Client, labels[config.LABEL_PG_CLUSTER], + _ = controller.SetClusterInitializedStatus(c.Client, labels[config.LABEL_PG_CLUSTER], job.ObjectMeta.Namespace) // now initialize the creation of any replica - controller.InitializeReplicaCreation(c.Client, labels[config.LABEL_PG_CLUSTER], + _ = controller.InitializeReplicaCreation(c.Client, labels[config.LABEL_PG_CLUSTER], job.ObjectMeta.Namespace) } else if labels[config.LABEL_PGHA_BACKUP_TYPE] == crv1.BackupTypeFailover { @@ -141,7 +138,7 @@ func (c *Controller) handleBackrestStanzaCreateUpdate(job *apiv1.Job) error { if cluster.Spec.Standby { log.Debugf("job Controller: standby cluster %s will now be set to an initialized "+ "status", clusterName) - controller.SetClusterInitializedStatus(c.Client, clusterName, namespace) + _ = controller.SetClusterInitializedStatus(c.Client, clusterName, namespace) return nil } @@ -153,7 +150,7 @@ func (c *Controller) handleBackrestStanzaCreateUpdate(job *apiv1.Job) error { return err } - backrest.CreateInitialBackup(c.Client, job.ObjectMeta.Namespace, + _, _ = backrest.CreateInitialBackup(c.Client, job.ObjectMeta.Namespace, clusterName, backrestRepoPodName) } diff --git a/internal/controller/job/bootstraphandler.go b/internal/controller/job/bootstraphandler.go index 580da57041..7b64937642 100644 --- a/internal/controller/job/bootstraphandler.go +++ b/internal/controller/job/bootstraphandler.go @@ -115,7 +115,7 @@ func (c *Controller) handleBootstrapUpdate(job *apiv1.Job) error { namespace, crv1.PgtaskWorkflowBackrestRestorePrimaryCreatedStatus); err != nil { log.Warn(err) } - publishRestoreComplete(labels[config.LABEL_PG_CLUSTER], labels[config.LABEL_PG_CLUSTER_IDENTIFIER], + publishRestoreComplete(labels[config.LABEL_PG_CLUSTER], labels[config.LABEL_PGOUSER], job.ObjectMeta.Namespace) } diff --git a/internal/controller/job/jobcontroller.go b/internal/controller/job/jobcontroller.go index 85e5e82c57..aa11399b47 100644 --- a/internal/controller/job/jobcontroller.go +++ b/internal/controller/job/jobcontroller.go @@ -33,11 +33,10 @@ type Controller struct { // onAdd is called when a postgresql operator job is created and an associated add event is // generated func (c *Controller) onAdd(obj interface{}) { - job := obj.(*apiv1.Job) labels := job.GetObjectMeta().GetLabels() - //only process jobs with with vendor=crunchydata label + // only process jobs with with vendor=crunchydata label if labels[config.LABEL_VENDOR] != "crunchydata" { return } @@ -48,12 +47,11 @@ func (c *Controller) onAdd(obj interface{}) { // onUpdate is called when a postgresql operator job is created and an associated update event is // generated func (c *Controller) onUpdate(oldObj, newObj interface{}) { - var err error job := newObj.(*apiv1.Job) labels := job.GetObjectMeta().GetLabels() - //only process jobs with with vendor=crunchydata label + // only process jobs with with vendor=crunchydata label if labels[config.LABEL_VENDOR] != "crunchydata" { return } @@ -69,7 +67,7 @@ func (c *Controller) onUpdate(oldObj, newObj interface{}) { err = c.handleRMDataUpdate(job) case labels[config.LABEL_BACKREST] == "true" || labels[config.LABEL_BACKREST_RESTORE] == "true": - err = c.handleBackrestUpdate(job) + c.handleBackrestUpdate(job) case labels[config.LABEL_BACKUP_TYPE_PGDUMP] == "true": err = c.handlePGDumpUpdate(job) case labels[config.LABEL_RESTORE_TYPE_PGRESTORE] == "true": @@ -85,11 +83,10 @@ func (c *Controller) onUpdate(oldObj, newObj interface{}) { // onDelete is called when a postgresql operator job is deleted func (c *Controller) onDelete(obj interface{}) { - job := obj.(*apiv1.Job) labels := job.GetObjectMeta().GetLabels() - //only process jobs with with vendor=crunchydata label + // only process jobs with with vendor=crunchydata label if labels[config.LABEL_VENDOR] != "crunchydata" { return } @@ -99,7 +96,6 @@ func (c *Controller) onDelete(obj interface{}) { // AddJobEventHandler adds the job event handler to the job informer func (c *Controller) AddJobEventHandler() { - c.Informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: c.onAdd, UpdateFunc: c.onUpdate, diff --git a/internal/controller/job/jobevents.go b/internal/controller/job/jobevents.go index ef4f1a1760..df21ba3ef6 100644 --- a/internal/controller/job/jobevents.go +++ b/internal/controller/job/jobevents.go @@ -22,7 +22,7 @@ import ( log "github.com/sirupsen/logrus" ) -func publishBackupComplete(clusterName, clusterIdentifier, username, backuptype, namespace, path string) { +func publishBackupComplete(clusterName, username, backuptype, namespace, path string) { topics := make([]string, 2) topics[0] = events.EventTopicCluster topics[1] = events.EventTopicBackup @@ -44,10 +44,9 @@ func publishBackupComplete(clusterName, clusterIdentifier, username, backuptype, if err != nil { log.Error(err.Error()) } - } -func publishRestoreComplete(clusterName, identifier, username, namespace string) { +func publishRestoreComplete(clusterName, username, namespace string) { topics := make([]string, 1) topics[0] = events.EventTopicCluster @@ -66,10 +65,9 @@ func publishRestoreComplete(clusterName, identifier, username, namespace string) if err != nil { log.Error(err.Error()) } - } -func publishDeleteClusterComplete(clusterName, identifier, username, namespace string) { +func publishDeleteClusterComplete(clusterName, username, namespace string) { topics := make([]string, 1) topics[0] = events.EventTopicCluster diff --git a/internal/controller/job/pgdumphandler.go b/internal/controller/job/pgdumphandler.go index 5b33285789..0fd25918b2 100644 --- a/internal/controller/job/pgdumphandler.go +++ b/internal/controller/job/pgdumphandler.go @@ -45,7 +45,7 @@ func (c *Controller) handlePGDumpUpdate(job *apiv1.Job) error { status = crv1.JobErrorStatus + " [" + job.ObjectMeta.Name + "]" } - //update the pgdump task status to submitted - updates task, not the job. + // update the pgdump task status to submitted - updates task, not the job. dumpTask := labels[config.LABEL_PGTASK] patch, err := kubeapi.NewJSONPatch().Add("spec", "status")(status).Bytes() if err == nil { @@ -81,7 +81,7 @@ func (c *Controller) handlePGRestoreUpdate(job *apiv1.Job) error { status = crv1.JobErrorStatus + " [" + job.ObjectMeta.Name + "]" } - //update the pgdump task status to submitted - updates task, not the job. + // update the pgdump task status to submitted - updates task, not the job. restoreTask := labels[config.LABEL_PGTASK] patch, err := kubeapi.NewJSONPatch().Add("spec", "status")(status).Bytes() if err == nil { diff --git a/internal/controller/job/rmdatahandler.go b/internal/controller/job/rmdatahandler.go index 0aa1624f7d..5fb8b0ed6c 100644 --- a/internal/controller/job/rmdatahandler.go +++ b/internal/controller/job/rmdatahandler.go @@ -48,7 +48,6 @@ func (c *Controller) handleRMDataUpdate(job *apiv1.Job) error { log.Debugf("jobController onUpdate rmdata job succeeded") publishDeleteClusterComplete(labels[config.LABEL_PG_CLUSTER], - job.ObjectMeta.Labels[config.LABEL_PG_CLUSTER_IDENTIFIER], job.ObjectMeta.Labels[config.LABEL_PGOUSER], job.ObjectMeta.Namespace) @@ -77,7 +76,7 @@ func (c *Controller) handleRMDataUpdate(job *apiv1.Job) error { return fmt.Errorf("could not remove Job %s for some reason after max tries", job.Name) } - //if a user has specified --archive for a cluster then + // if a user has specified --archive for a cluster then // an xlog PVC will be present and can be removed pvcName := clusterName + "-xlog" if err := pvc.DeleteIfExists(c.Client.Clientset, pvcName, job.Namespace); err != nil { @@ -85,7 +84,7 @@ func (c *Controller) handleRMDataUpdate(job *apiv1.Job) error { return err } - //delete any completed jobs for this cluster as a cleanup + // delete any completed jobs for this cluster as a cleanup jobList, err := c.Client. BatchV1().Jobs(job.Namespace). List(ctx, metav1.ListOptions{LabelSelector: config.LABEL_PG_CLUSTER + "=" + clusterName}) diff --git a/internal/controller/manager/controllermanager.go b/internal/controller/manager/controllermanager.go index 236ed1bb74..4a6ac9dc2c 100644 --- a/internal/controller/manager/controllermanager.go +++ b/internal/controller/manager/controllermanager.go @@ -84,7 +84,6 @@ type controllerGroup struct { func NewControllerManager(namespaces []string, pgoConfig config.PgoConfig, pgoNamespace, installationName string, namespaceOperatingMode ns.NamespaceOperatingMode) (*ControllerManager, error) { - controllerManager := ControllerManager{ controllers: make(map[string]*controllerGroup), installationName: installationName, @@ -123,7 +122,6 @@ func NewControllerManager(namespaces []string, // easily started as needed). Each controller group also receives its own clients, which can then // be utilized by the various controllers within that controller group. func (c *ControllerManager) AddGroup(namespace string) error { - c.mgrMutex.Lock() defer c.mgrMutex.Unlock() @@ -139,7 +137,6 @@ func (c *ControllerManager) AddGroup(namespace string) error { // AddAndRunGroup is a convenience function that adds a controller group for the // namespace specified, and then immediately runs the controllers in that group. func (c *ControllerManager) AddAndRunGroup(namespace string) error { - if c.controllers[namespace] != nil && !c.pgoConfig.Pgo.DisableReconcileRBAC { // first reconcile RBAC in the target namespace if RBAC reconciliation is enabled c.reconcileRBAC(namespace) @@ -165,7 +162,6 @@ func (c *ControllerManager) AddAndRunGroup(namespace string) error { // RemoveAll removes all controller groups managed by the controller manager, first stopping all // controllers within each controller group managed by the controller manager. func (c *ControllerManager) RemoveAll() { - c.mgrMutex.Lock() defer c.mgrMutex.Unlock() @@ -179,7 +175,6 @@ func (c *ControllerManager) RemoveAll() { // RemoveGroup removes the controller group for the namespace specified, first stopping all // controllers within that group func (c *ControllerManager) RemoveGroup(namespace string) { - c.mgrMutex.Lock() defer c.mgrMutex.Unlock() @@ -188,7 +183,6 @@ func (c *ControllerManager) RemoveGroup(namespace string) { // RunAll runs all controllers across all controller groups managed by the controller manager. func (c *ControllerManager) RunAll() error { - c.mgrMutex.Lock() defer c.mgrMutex.Unlock() @@ -205,7 +199,6 @@ func (c *ControllerManager) RunAll() error { // RunGroup runs the controllers within the controller group for the namespace specified. func (c *ControllerManager) RunGroup(namespace string) error { - c.mgrMutex.Lock() defer c.mgrMutex.Unlock() @@ -226,7 +219,6 @@ func (c *ControllerManager) RunGroup(namespace string) error { // addControllerGroup adds a new controller group for the namespace specified func (c *ControllerManager) addControllerGroup(namespace string) error { - if _, ok := c.controllers[namespace]; ok { log.Debugf("Controller Manager: a controller for namespace %s already exists", namespace) return controller.ErrControllerGroupExists @@ -340,7 +332,6 @@ func (c *ControllerManager) addControllerGroup(namespace string) error { // hasListerPrivs verifies the Operator has the privileges required to start the controllers // for the namespace specified. func (c *ControllerManager) hasListerPrivs(namespace string) bool { - controllerGroup := c.controllers[namespace] var err error @@ -389,7 +380,6 @@ func (c *ControllerManager) hasListerPrivs(namespace string) bool { // runControllerGroup is responsible running the controllers for the controller group corresponding // to the namespace provided func (c *ControllerManager) runControllerGroup(namespace string) error { - controllerGroup := c.controllers[namespace] hasListerPrivs := c.hasListerPrivs(namespace) @@ -442,7 +432,6 @@ func (c *ControllerManager) runControllerGroup(namespace string) error { // queues associated with the controllers inside of the controller group are first shutdown // prior to removing the controller group. func (c *ControllerManager) removeControllerGroup(namespace string) { - if _, ok := c.controllers[namespace]; !ok { log.Debugf("Controller Manager: no controller group to remove for ns %s", namespace) return @@ -458,7 +447,6 @@ func (c *ControllerManager) removeControllerGroup(namespace string) { // done by calling the ShutdownWorker function associated with the controller. If the controller // does not have a ShutdownWorker function then no action is taken. func (c *ControllerManager) stopControllerGroup(namespace string) { - if _, ok := c.controllers[namespace]; !ok { log.Debugf("Controller Manager: unable to stop controller group for namespace %s because "+ "a controller group for this namespace does not exist", namespace) diff --git a/internal/controller/manager/rbac.go b/internal/controller/manager/rbac.go index bb972e5202..8cad4ff247 100644 --- a/internal/controller/manager/rbac.go +++ b/internal/controller/manager/rbac.go @@ -83,7 +83,6 @@ func (c *ControllerManager) reconcileRBAC(targetNamespace string) { // reconcileRoles reconciles the Roles required by the operator in a target namespace func (c *ControllerManager) reconcileRoles(targetNamespace string) { - reconcileRoles := map[string]*template.Template{ ns.PGO_TARGET_ROLE: config.PgoTargetRoleTemplate, ns.PGO_BACKREST_ROLE: config.PgoBackrestRoleTemplate, @@ -101,7 +100,6 @@ func (c *ControllerManager) reconcileRoles(targetNamespace string) { // reconcileRoleBindings reconciles the RoleBindings required by the operator in a // target namespace func (c *ControllerManager) reconcileRoleBindings(targetNamespace string) { - reconcileRoleBindings := map[string]*template.Template{ ns.PGO_TARGET_ROLE_BINDING: config.PgoTargetRoleBindingTemplate, ns.PGO_BACKREST_ROLE_BINDING: config.PgoBackrestRoleBindingTemplate, @@ -120,7 +118,6 @@ func (c *ControllerManager) reconcileRoleBindings(targetNamespace string) { // target namespace func (c *ControllerManager) reconcileServiceAccounts(targetNamespace string, imagePullSecrets []v1.LocalObjectReference) (saCreatedOrUpdated bool) { - reconcileServiceAccounts := map[string]*template.Template{ ns.PGO_DEFAULT_SERVICE_ACCOUNT: config.PgoDefaultServiceAccountTemplate, ns.PGO_TARGET_SERVICE_ACCOUNT: config.PgoTargetServiceAccountTemplate, diff --git a/internal/controller/namespace/namespacecontroller.go b/internal/controller/namespace/namespacecontroller.go index 6fc85f644f..609a0715d0 100644 --- a/internal/controller/namespace/namespacecontroller.go +++ b/internal/controller/namespace/namespacecontroller.go @@ -43,7 +43,6 @@ type Controller struct { // PostgreSQL Operator are added and deleted. func NewNamespaceController(controllerManager controller.Manager, informer coreinformers.NamespaceInformer, workerCount int) (*Controller, error) { - controller := &Controller{ ControllerManager: controllerManager, Informer: informer, @@ -72,7 +71,6 @@ func NewNamespaceController(controllerManager controller.Manager, // function in order to read and process a message on the worker queue. Once the worker queue // is instructed to shutdown, a message is written to the done channel. func (c *Controller) RunWorker(stopCh <-chan struct{}) { - go c.waitForShutdown(stopCh) for c.processNextWorkItem() { @@ -96,7 +94,6 @@ func (c *Controller) ShutdownWorker() { // so, the namespace resource is converted into a namespace/name string and is then added to the // work queue func (c *Controller) enqueueNamespace(obj interface{}) { - var key string var err error if key, err = cache.MetaNamespaceKeyFunc(obj); err != nil { @@ -109,7 +106,6 @@ func (c *Controller) enqueueNamespace(obj interface{}) { // processNextWorkItem will read a single work item off the work queue and processes it via // the Namespace sync handler func (c *Controller) processNextWorkItem() bool { - obj, shutdown := c.workqueue.Get() if shutdown { diff --git a/internal/controller/pgcluster/pgclustercontroller.go b/internal/controller/pgcluster/pgclustercontroller.go index 24a6b78a6b..5c745fec7c 100644 --- a/internal/controller/pgcluster/pgclustercontroller.go +++ b/internal/controller/pgcluster/pgclustercontroller.go @@ -63,7 +63,6 @@ func (c *Controller) onAdd(obj interface{}) { // processNextWorkItem function in order to read and process a message on the // workqueue. func (c *Controller) RunWorker(stopCh <-chan struct{}, doneCh chan<- struct{}) { - go c.waitForShutdown(stopCh) for c.processNextItem() { @@ -101,7 +100,7 @@ func (c *Controller) processNextItem() bool { // parallel. defer c.Queue.Done(key) - //get the pgcluster + // get the pgcluster cluster, err := c.Client.CrunchydataV1().Pgclusters(keyNamespace).Get(ctx, keyResourceName, metav1.GetOptions{}) if err != nil { log.Debugf("cluster add - pgcluster not found, this is invalid") @@ -196,10 +195,10 @@ func (c *Controller) onUpdate(oldObj, newObj interface{}) { // shutdown or started but its current status does not properly reflect that it is, then // proceed with the logic needed to either shutdown or start the cluster if newcluster.Spec.Shutdown && newcluster.Status.State != crv1.PgclusterStateShutdown { - clusteroperator.ShutdownCluster(c.Client, *newcluster) + _ = clusteroperator.ShutdownCluster(c.Client, *newcluster) } else if !newcluster.Spec.Shutdown && newcluster.Status.State == crv1.PgclusterStateShutdown { - clusteroperator.StartupCluster(c.Client, *newcluster) + _ = clusteroperator.StartupCluster(c.Client, *newcluster) } // check to see if the "autofail" label on the pgcluster CR has been changed from either true to false, or from @@ -217,7 +216,7 @@ func (c *Controller) onUpdate(oldObj, newObj interface{}) { return } if autofailEnabledNew != autofailEnabledOld { - util.ToggleAutoFailover(c.Client, autofailEnabledNew, + _ = util.ToggleAutoFailover(c.Client, autofailEnabledNew, newcluster.ObjectMeta.Labels[config.LABEL_PGHA_SCOPE], newcluster.ObjectMeta.Namespace) } @@ -329,16 +328,15 @@ func (c *Controller) onUpdate(oldObj, newObj interface{}) { // onDelete is called when a pgcluster is deleted func (c *Controller) onDelete(obj interface{}) { - //cluster := obj.(*crv1.Pgcluster) + // cluster := obj.(*crv1.Pgcluster) // log.Debugf("[Controller] ns=%s onDelete %s", cluster.ObjectMeta.Namespace, cluster.ObjectMeta.SelfLink) - //handle pgcluster cleanup + // handle pgcluster cleanup // clusteroperator.DeleteClusterBase(c.PgclusterClientset, c.PgclusterClient, cluster, cluster.ObjectMeta.Namespace) } // AddPGClusterEventHandler adds the pgcluster event handler to the pgcluster informer func (c *Controller) AddPGClusterEventHandler() { - c.Informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: c.onAdd, UpdateFunc: c.onUpdate, @@ -466,7 +464,6 @@ func updatePgBouncer(c *Controller, oldCluster *crv1.Pgcluster, newCluster *crv1 func updateTablespaces(c *Controller, oldCluster *crv1.Pgcluster, newCluster *crv1.Pgcluster) error { // first, get a list of all of the instance deployments for the cluster deployments, err := operator.GetInstanceDeployments(c.Client, newCluster) - if err != nil { return err } diff --git a/internal/controller/pgpolicy/pgpolicycontroller.go b/internal/controller/pgpolicy/pgpolicycontroller.go index a9eef2e3ec..27d640475b 100644 --- a/internal/controller/pgpolicy/pgpolicycontroller.go +++ b/internal/controller/pgpolicy/pgpolicycontroller.go @@ -44,8 +44,8 @@ func (c *Controller) onAdd(obj interface{}) { policy := obj.(*crv1.Pgpolicy) log.Debugf("[pgpolicy Controller] onAdd ns=%s %s", policy.ObjectMeta.Namespace, policy.ObjectMeta.SelfLink) - //handle the case of when a pgpolicy is already processed, which - //is the case when the operator restarts + // handle the case of when a pgpolicy is already processed, which + // is the case when the operator restarts if policy.Status.State == crv1.PgpolicyStateProcessed { log.Debug("pgpolicy " + policy.ObjectMeta.Name + " already processed") return @@ -65,7 +65,7 @@ func (c *Controller) onAdd(obj interface{}) { log.Errorf("ERROR updating pgpolicy status: %s", err.Error()) } - //publish event + // publish event topics := make([]string, 1) topics[0] = events.EventTopicPolicy @@ -84,7 +84,6 @@ func (c *Controller) onAdd(obj interface{}) { if err != nil { log.Error(err.Error()) } - } // onUpdate is called when a pgpolicy is updated @@ -98,7 +97,7 @@ func (c *Controller) onDelete(obj interface{}) { log.Debugf("DELETED pgpolicy %s", policy.ObjectMeta.Name) - //publish event + // publish event topics := make([]string, 1) topics[0] = events.EventTopicPolicy @@ -117,12 +116,10 @@ func (c *Controller) onDelete(obj interface{}) { if err != nil { log.Error(err.Error()) } - } // AddPGPolicyEventHandler adds the pgpolicy event handler to the pgpolicy informer func (c *Controller) AddPGPolicyEventHandler() { - c.Informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: c.onAdd, UpdateFunc: c.onUpdate, diff --git a/internal/controller/pgreplica/pgreplicacontroller.go b/internal/controller/pgreplica/pgreplicacontroller.go index e3d10128c9..91325b7066 100644 --- a/internal/controller/pgreplica/pgreplicacontroller.go +++ b/internal/controller/pgreplica/pgreplicacontroller.go @@ -44,7 +44,6 @@ type Controller struct { // processNextWorkItem function in order to read and process a message on the // workqueue. func (c *Controller) RunWorker(stopCh <-chan struct{}, doneCh chan<- struct{}) { - go c.waitForShutdown(stopCh) for c.processNextItem() { @@ -96,8 +95,8 @@ func (c *Controller) processNextItem() bool { } else { log.Debugf("working...no replica found, means we process") - //handle the case of when a pgreplica is added which is - //scaling up a cluster + // handle the case of when a pgreplica is added which is + // scaling up a cluster replica, err := c.Clientset.CrunchydataV1().Pgreplicas(keyNamespace).Get(ctx, keyResourceName, metav1.GetOptions{}) if err != nil { log.Error(err) @@ -155,8 +154,8 @@ func (c *Controller) processNextItem() bool { func (c *Controller) onAdd(obj interface{}) { replica := obj.(*crv1.Pgreplica) - //handle the case of pgreplicas being processed already and - //when the operator restarts + // handle the case of pgreplicas being processed already and + // when the operator restarts if replica.Status.State == crv1.PgreplicaStateProcessed { log.Debug("pgreplica " + replica.ObjectMeta.Name + " already processed") return @@ -167,7 +166,6 @@ func (c *Controller) onAdd(obj interface{}) { log.Debugf("onAdd putting key in queue %s", key) c.Queue.Add(key) } - } // onUpdate is called when a pgreplica is updated @@ -215,26 +213,24 @@ func (c *Controller) onDelete(obj interface{}) { replica := obj.(*crv1.Pgreplica) log.Debugf("[pgreplica Controller] OnDelete ns=%s %s", replica.ObjectMeta.Namespace, replica.ObjectMeta.SelfLink) - //make sure we are not removing a replica deployment - //that is now the primary after a failover + // make sure we are not removing a replica deployment + // that is now the primary after a failover dep, err := c.Clientset. AppsV1().Deployments(replica.ObjectMeta.Namespace). Get(ctx, replica.Spec.Name, metav1.GetOptions{}) if err == nil { if dep.ObjectMeta.Labels[config.LABEL_SERVICE_NAME] == dep.ObjectMeta.Labels[config.LABEL_PG_CLUSTER] { - //the replica was made a primary at some point - //we will not scale down the deployment + // the replica was made a primary at some point + // we will not scale down the deployment log.Debugf("[pgreplica Controller] OnDelete not scaling down the replica since it is acting as a primary") } else { clusteroperator.ScaleDownBase(c.Clientset, replica, replica.ObjectMeta.Namespace) } } - } // AddPGReplicaEventHandler adds the pgreplica event handler to the pgreplica informer func (c *Controller) AddPGReplicaEventHandler() { - // Your custom resource event handlers. c.Informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: c.onAdd, diff --git a/internal/controller/pgtask/pgtaskcontroller.go b/internal/controller/pgtask/pgtaskcontroller.go index 0a60af9eb6..788de4606a 100644 --- a/internal/controller/pgtask/pgtaskcontroller.go +++ b/internal/controller/pgtask/pgtaskcontroller.go @@ -50,7 +50,6 @@ type Controller struct { // processNextWorkItem function in order to read and process a message on the // workqueue. func (c *Controller) RunWorker(stopCh <-chan struct{}, doneCh chan<- struct{}) { - go c.waitForShutdown(stopCh) for c.processNextItem() { @@ -95,7 +94,7 @@ func (c *Controller) processNextItem() bool { return true } - //update pgtask + // update pgtask patch, err := json.Marshal(map[string]interface{}{ "status": crv1.PgtaskStatus{ State: crv1.PgtaskStateProcessed, @@ -112,7 +111,7 @@ func (c *Controller) processNextItem() bool { return true } - //process the incoming task + // process the incoming task switch tmpTask.Spec.TaskType { case crv1.PgtaskPgAdminAdd: log.Debug("add pgadmin task added") @@ -152,9 +151,6 @@ func (c *Controller) processNextItem() bool { } else { log.Debugf("skipping duplicate onAdd delete data task %s/%s", keyNamespace, keyResourceName) } - case crv1.PgtaskDeleteBackups: - log.Debug("delete backups task added") - taskoperator.RemoveBackups(keyNamespace, c.Client, tmpTask) case crv1.PgtaskBackrest: log.Debug("backrest task added") backrestoperator.Backrest(keyNamespace, c.Client, tmpTask) @@ -180,15 +176,14 @@ func (c *Controller) processNextItem() bool { c.Queue.Forget(key) return true - } // onAdd is called when a pgtask is added func (c *Controller) onAdd(obj interface{}) { task := obj.(*crv1.Pgtask) - //handle the case of when the operator restarts, we do not want - //to process pgtasks already processed + // handle the case of when the operator restarts, we do not want + // to process pgtasks already processed if task.Status.State == crv1.PgtaskStateProcessed { log.Debug("pgtask " + task.ObjectMeta.Name + " already processed") return @@ -199,12 +194,11 @@ func (c *Controller) onAdd(obj interface{}) { log.Debugf("task putting key in queue %s", key) c.Queue.Add(key) } - } // onUpdate is called when a pgtask is updated func (c *Controller) onUpdate(oldObj, newObj interface{}) { - //task := newObj.(*crv1.Pgtask) + // task := newObj.(*crv1.Pgtask) // log.Debugf("[Controller] onUpdate ns=%s %s", task.ObjectMeta.Namespace, task.ObjectMeta.SelfLink) } @@ -214,7 +208,6 @@ func (c *Controller) onDelete(obj interface{}) { // AddPGTaskEventHandler adds the pgtask event handler to the pgtask informer func (c *Controller) AddPGTaskEventHandler() { - c.Informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: c.onAdd, UpdateFunc: c.onUpdate, @@ -224,14 +217,14 @@ func (c *Controller) AddPGTaskEventHandler() { log.Debugf("pgtask Controller: added event handler to informer") } -//de-dupe logic for a failover, if the failover started -//parameter is set, it means a failover has already been -//started on this +// de-dupe logic for a failover, if the failover started +// parameter is set, it means a failover has already been +// started on this func dupeFailover(clientset pgo.Interface, task *crv1.Pgtask, ns string) bool { ctx := context.TODO() tmp, err := clientset.CrunchydataV1().Pgtasks(ns).Get(ctx, task.Spec.Name, metav1.GetOptions{}) if err != nil { - //a big time error if this occurs + // a big time error if this occurs return false } @@ -242,14 +235,14 @@ func dupeFailover(clientset pgo.Interface, task *crv1.Pgtask, ns string) bool { return true } -//de-dupe logic for a delete data, if the delete data job started -//parameter is set, it means a delete data job has already been -//started on this +// de-dupe logic for a delete data, if the delete data job started +// parameter is set, it means a delete data job has already been +// started on this func dupeDeleteData(clientset pgo.Interface, task *crv1.Pgtask, ns string) bool { ctx := context.TODO() tmp, err := clientset.CrunchydataV1().Pgtasks(ns).Get(ctx, task.Spec.Name, metav1.GetOptions{}) if err != nil { - //a big time error if this occurs + // a big time error if this occurs return false } diff --git a/internal/controller/pod/inithandler.go b/internal/controller/pod/inithandler.go index c33be8d2ed..2f09dbef3c 100644 --- a/internal/controller/pod/inithandler.go +++ b/internal/controller/pod/inithandler.go @@ -40,7 +40,6 @@ import ( // handleClusterInit is responsible for proceeding with initialization of the PG cluster once the // primary PG pod for a new or restored PG cluster reaches a ready status func (c *Controller) handleClusterInit(newPod *apiv1.Pod, cluster *crv1.Pgcluster) error { - clusterName := cluster.GetName() // first check to see if the update is a repo pod. If so, then call repo init handler and @@ -76,7 +75,6 @@ func (c *Controller) handleClusterInit(newPod *apiv1.Pod, cluster *crv1.Pgcluste // handleBackRestRepoInit handles cluster initialization tasks that must be executed once // as a result of an update to a cluster's pgBackRest repository pod func (c *Controller) handleBackRestRepoInit(newPod *apiv1.Pod, cluster *crv1.Pgcluster) error { - // if the repo pod is for a cluster bootstrap, the kick of the bootstrap job and return if _, ok := newPod.GetLabels()[config.LABEL_PGHA_BOOTSTRAP]; ok { if err := clusteroperator.AddClusterBootstrap(c.Client, cluster); err != nil { @@ -103,7 +101,6 @@ func (c *Controller) handleBackRestRepoInit(newPod *apiv1.Pod, cluster *crv1.Pgc // regardless of the specific type of cluster (e.g. regualar or standby) or the reason the // cluster is being initialized (initial bootstrap or restore) func (c *Controller) handleCommonInit(cluster *crv1.Pgcluster) error { - // Disable autofailover in the cluster that is now "Ready" if the autofail label is set // to "false" on the pgcluster (i.e. label "autofail=true") autofailEnabled, err := strconv.ParseBool(cluster.ObjectMeta.Labels[config.LABEL_AUTOFAIL]) @@ -111,7 +108,7 @@ func (c *Controller) handleCommonInit(cluster *crv1.Pgcluster) error { log.Error(err) return err } else if !autofailEnabled { - util.ToggleAutoFailover(c.Client, false, + _ = util.ToggleAutoFailover(c.Client, false, cluster.ObjectMeta.Labels[config.LABEL_PGHA_SCOPE], cluster.Namespace) } @@ -151,9 +148,8 @@ func (c *Controller) handleBootstrapInit(newPod *apiv1.Pod, cluster *crv1.Pgclus taskoperator.CompleteCreateClusterWorkflow(clusterName, c.Client, namespace) - //publish event for cluster complete - publishClusterComplete(clusterName, namespace, cluster) - // + // publish event for cluster complete + _ = publishClusterComplete(clusterName, namespace, cluster) // first clean any stanza create resources from a previous stanza-create, e.g. during a // restore when these resources may already exist from initial creation of the cluster @@ -187,12 +183,11 @@ func (c *Controller) handleStandbyInit(cluster *crv1.Pgcluster) error { taskoperator.CompleteCreateClusterWorkflow(clusterName, c.Client, namespace) - //publish event for cluster complete - publishClusterComplete(clusterName, namespace, cluster) - // + // publish event for cluster complete + _ = publishClusterComplete(clusterName, namespace, cluster) // now scale any replicas deployments to 1 - clusteroperator.ScaleClusterDeployments(c.Client, *cluster, 1, false, true, false, false) + _, _ = clusteroperator.ScaleClusterDeployments(c.Client, *cluster, 1, false, true, false, false) // Proceed with stanza-creation of this is not a standby cluster, or if its // a standby cluster that does not have "s3" storage only enabled. @@ -214,7 +209,7 @@ func (c *Controller) handleStandbyInit(cluster *crv1.Pgcluster) error { } backrestoperator.StanzaCreate(namespace, clusterName, c.Client) } else { - controller.SetClusterInitializedStatus(c.Client, clusterName, namespace) + _ = controller.SetClusterInitializedStatus(c.Client, clusterName, namespace) } // If a standby cluster initialize the creation of any replicas. Replicas @@ -222,7 +217,7 @@ func (c *Controller) handleStandbyInit(cluster *crv1.Pgcluster) error { // stanza-creation and/or the creation of any backups, since the replicas // will be generated from the pgBackRest repository of an external PostgreSQL // database (which should already exist). - controller.InitializeReplicaCreation(c.Client, clusterName, namespace) + _ = controller.InitializeReplicaCreation(c.Client, clusterName, namespace) // if this is a pgbouncer enabled cluster, add a pgbouncer // Note: we only warn if we cannot create the pgBouncer, so eecution can @@ -262,13 +257,13 @@ func (c *Controller) labelPostgresPodAndDeployment(newpod *apiv1.Pod) { log.Debug("which means its pod was restarted for some reason") log.Debug("we will use the service name on the deployment") serviceName = dep.ObjectMeta.Labels[config.LABEL_SERVICE_NAME] - } else if replica == false { + } else if !replica { log.Debugf("primary pod ADDED %s service-name=%s", newpod.Name, newpod.ObjectMeta.Labels[config.LABEL_PG_CLUSTER]) - //add label onto pod "service-name=clustername" + // add label onto pod "service-name=clustername" serviceName = newpod.ObjectMeta.Labels[config.LABEL_PG_CLUSTER] - } else if replica == true { + } else if replica { log.Debugf("replica pod ADDED %s service-name=%s", newpod.Name, newpod.ObjectMeta.Labels[config.LABEL_PG_CLUSTER]+"-replica") - //add label onto pod "service-name=clustername-replica" + // add label onto pod "service-name=clustername-replica" serviceName = newpod.ObjectMeta.Labels[config.LABEL_PG_CLUSTER] + "-replica" } @@ -283,12 +278,11 @@ func (c *Controller) labelPostgresPodAndDeployment(newpod *apiv1.Pod) { return } - //add the service name label to the Deployment + // add the service name label to the Deployment log.Debugf("patching deployment %s: %s", dep.Name, patch) _, err = c.Client.AppsV1().Deployments(ns).Patch(ctx, dep.Name, types.MergePatchType, patch, metav1.PatchOptions{}) if err != nil { log.Error("could not add label to deployment on pod add") return } - } diff --git a/internal/controller/pod/podcontroller.go b/internal/controller/pod/podcontroller.go index 95b916df5e..95d81bea32 100644 --- a/internal/controller/pod/podcontroller.go +++ b/internal/controller/pod/podcontroller.go @@ -40,17 +40,16 @@ type Controller struct { // onAdd is called when a pod is added func (c *Controller) onAdd(obj interface{}) { - newPod := obj.(*apiv1.Pod) newPodLabels := newPod.GetObjectMeta().GetLabels() - //only process pods with with vendor=crunchydata label + // only process pods with with vendor=crunchydata label if newPodLabels[config.LABEL_VENDOR] == "crunchydata" { log.Debugf("Pod Controller: onAdd processing the addition of pod %s in namespace %s", newPod.Name, newPod.Namespace) } - //handle the case when a pg database pod is added + // handle the case when a pg database pod is added if isPostgresPod(newPod) { c.labelPostgresPodAndDeployment(newPod) return @@ -65,7 +64,7 @@ func (c *Controller) onUpdate(oldObj, newObj interface{}) { newPodLabels := newPod.GetObjectMeta().GetLabels() - //only process pods with with vendor=crunchydata label + // only process pods with with vendor=crunchydata label if newPodLabels[config.LABEL_VENDOR] != "crunchydata" { return } @@ -153,7 +152,6 @@ func setCurrentPrimary(clientset pgo.Interface, newPod *apiv1.Pod, cluster *crv1 // onDelete is called when a pgcluster is deleted func (c *Controller) onDelete(obj interface{}) { - pod := obj.(*apiv1.Pod) labels := pod.GetObjectMeta().GetLabels() @@ -165,7 +163,6 @@ func (c *Controller) onDelete(obj interface{}) { // AddPodEventHandler adds the pod event handler to the pod informer func (c *Controller) AddPodEventHandler() { - c.Informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: c.onAdd, UpdateFunc: c.onUpdate, @@ -190,7 +187,6 @@ func isBackRestRepoBecomingReady(oldPod, newPod *apiv1.Pod) bool { // assumed to be present), specifically because this label will only be included on pgBackRest // repository Pods. func isBackRestRepoPod(newpod *apiv1.Pod) bool { - _, backrestRepoLabelExists := newpod.ObjectMeta.Labels[config.LABEL_PGO_BACKREST_REPO] return backrestRepoLabelExists @@ -237,7 +233,6 @@ func isDBContainerBecomingReady(oldPod, newPod *apiv1.Pod) bool { // this label will only be included on primary and replica PostgreSQL database pods (and will be // present as soon as the deployment and pod is created). func isPostgresPod(newpod *apiv1.Pod) bool { - _, pgDatabaseLabelExists := newpod.ObjectMeta.Labels[config.LABEL_PG_DATABASE] return pgDatabaseLabelExists diff --git a/internal/controller/pod/podevents.go b/internal/controller/pod/podevents.go index 100175baed..b3086355ca 100644 --- a/internal/controller/pod/podevents.go +++ b/internal/controller/pod/podevents.go @@ -25,7 +25,7 @@ import ( ) func publishClusterComplete(clusterName, namespace string, cluster *crv1.Pgcluster) error { - //capture the cluster creation event + // capture the cluster creation event topics := make([]string, 1) topics[0] = events.EventTopicCluster @@ -47,5 +47,4 @@ func publishClusterComplete(clusterName, namespace string, cluster *crv1.Pgclust return err } return err - } diff --git a/internal/controller/pod/promotionhandler.go b/internal/controller/pod/promotionhandler.go index e420af589c..a1eb83530a 100644 --- a/internal/controller/pod/promotionhandler.go +++ b/internal/controller/pod/promotionhandler.go @@ -62,7 +62,6 @@ var ( // of a failover. Specifically, this handler is triggered when a replica has been promoted, and // it now has either the "promoted" or "primary" role label. func (c *Controller) handlePostgresPodPromotion(newPod *apiv1.Pod, cluster crv1.Pgcluster) error { - if cluster.Status.State == crv1.PgclusterStateShutdown { if err := c.handleStartupInit(cluster); err != nil { return err @@ -84,7 +83,6 @@ func (c *Controller) handlePostgresPodPromotion(newPod *apiv1.Pod, cluster crv1. // handleStartupInit is resposible for handling cluster initilization for a cluster that has been // restarted (after it was previously shutdown) func (c *Controller) handleStartupInit(cluster crv1.Pgcluster) error { - // since the cluster is just being restarted, it can just be set to initialized once the // primary is ready if err := controller.SetClusterInitializedStatus(c.Client, cluster.Name, @@ -94,7 +92,7 @@ func (c *Controller) handleStartupInit(cluster crv1.Pgcluster) error { } // now scale any replicas deployments to 1 - clusteroperator.ScaleClusterDeployments(c.Client, cluster, 1, false, true, false, false) + _, _ = clusteroperator.ScaleClusterDeployments(c.Client, cluster, 1, false, true, false, false) return nil } @@ -103,7 +101,6 @@ func (c *Controller) handleStartupInit(cluster crv1.Pgcluster) error { // of disabling standby mode. Specifically, this handler is triggered when a standby leader // is turned into a regular leader. func (c *Controller) handleStandbyPromotion(newPod *apiv1.Pod, cluster crv1.Pgcluster) error { - clusterName := cluster.Name namespace := cluster.Namespace @@ -141,7 +138,6 @@ func (c *Controller) handleStandbyPromotion(newPod *apiv1.Pod, cluster crv1.Pgcl // done by confirming func waitForStandbyPromotion(restConfig *rest.Config, clientset kubernetes.Interface, newPod apiv1.Pod, cluster crv1.Pgcluster) error { - var recoveryDisabled bool // wait for the server to accept writes to ensure standby has truly been disabled before @@ -183,7 +179,7 @@ func waitForStandbyPromotion(restConfig *rest.Config, clientset kubernetes.Inter func cleanAndCreatePostFailoverBackup(clientset kubeapi.Interface, clusterName, namespace string) error { ctx := context.TODO() - //look up the backrest-repo pod name + // look up the backrest-repo pod name selector := fmt.Sprintf("%s=%s,%s=true", config.LABEL_PG_CLUSTER, clusterName, config.LABEL_PGO_BACKREST_REPO) pods, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{LabelSelector: selector}) diff --git a/internal/kubeapi/client_config.go b/internal/kubeapi/client_config.go index 22fe39e9b2..b3eeed0c2a 100644 --- a/internal/kubeapi/client_config.go +++ b/internal/kubeapi/client_config.go @@ -37,8 +37,10 @@ type Interface interface { } // Interface should satisfy both our typed Interface and the standard one. -var _ crunchydata.Interface = Interface(nil) -var _ kubernetes.Interface = Interface(nil) +var ( + _ crunchydata.Interface = Interface(nil) + _ kubernetes.Interface = Interface(nil) +) // Client provides methods for interacting with Kubernetes resources. // It implements both kubernetes and crunchydata clientset Interfaces. diff --git a/internal/kubeapi/fake/fakeclients.go b/internal/kubeapi/fake/fakeclients.go index 6a263818d4..8c7395549d 100644 --- a/internal/kubeapi/fake/fakeclients.go +++ b/internal/kubeapi/fake/fakeclients.go @@ -55,7 +55,6 @@ var ( // initialization of the Operator in various unit tests where the various resources loaded // during initialization (e.g. templates, config and/or global variables) are required. func NewFakePGOClient() (kubeapi.Interface, error) { - if pgoRoot == "" { return nil, errors.New("Environment variable PGOROOT must be set to the root directory " + "of the PostgreSQL Operator project repository in order to create a fake client") @@ -84,7 +83,6 @@ func NewFakePGOClient() (kubeapi.Interface, error) { // utilized when testing to similate and environment containing the various PostgreSQL Operator // configuration files (e.g. templates) required to run the Operator. func createMockPGOConfigMap(pgoNamespace string) (*v1.ConfigMap, error) { - // create a configMap that will hold the default configs pgoConfigMap := &v1.ConfigMap{ Data: make(map[string]string), diff --git a/internal/kubeapi/volumes_test.go b/internal/kubeapi/volumes_test.go index b793ac5269..c2933d9e87 100644 --- a/internal/kubeapi/volumes_test.go +++ b/internal/kubeapi/volumes_test.go @@ -26,7 +26,7 @@ func TestFindOrAppendVolume(t *testing.T) { t.Run("empty", func(t *testing.T) { var volumes []v1.Volume - var volume = FindOrAppendVolume(&volumes, "v1") + volume := FindOrAppendVolume(&volumes, "v1") if expected, actual := 1, len(volumes); expected != actual { t.Fatalf("expected appended volume, got %v", actual) } @@ -69,7 +69,7 @@ func TestFindOrAppendVolumeMount(t *testing.T) { t.Run("empty", func(t *testing.T) { var mounts []v1.VolumeMount - var mount = FindOrAppendVolumeMount(&mounts, "v1") + mount := FindOrAppendVolumeMount(&mounts, "v1") if expected, actual := 1, len(mounts); expected != actual { t.Fatalf("expected appended mount, got %v", actual) } diff --git a/internal/logging/loglib.go b/internal/logging/loglib.go index b443e47b4d..e346317f6e 100644 --- a/internal/logging/loglib.go +++ b/internal/logging/loglib.go @@ -1,4 +1,4 @@ -//Package logging Functions to set unique configuration for use with the logrus logger +// Package logging Functions to set unique configuration for use with the logrus logger package logging /* @@ -34,7 +34,7 @@ func SetParameters() LogValues { return logval } -//LogValues holds the standard log value types +// LogValues holds the standard log value types type LogValues struct { version string } @@ -53,9 +53,9 @@ func (f *formatter) Format(e *log.Entry) ([]byte, error) { return f.lf.Format(e) } -//CrunchyLogger adds the customized logging fields to the logrus instance context +// CrunchyLogger adds the customized logging fields to the logrus instance context func CrunchyLogger(logDetails LogValues) { - //Sets calling method as a field + // Sets calling method as a field log.SetReportCaller(true) crunchyTextFormatter := &log.TextFormatter{ diff --git a/internal/ns/nslogic.go b/internal/ns/nslogic.go index 21f0499b7f..34df014bed 100644 --- a/internal/ns/nslogic.go +++ b/internal/ns/nslogic.go @@ -43,20 +43,28 @@ import ( "k8s.io/client-go/kubernetes/fake" ) -const OPERATOR_SERVICE_ACCOUNT = "postgres-operator" -const PGO_DEFAULT_SERVICE_ACCOUNT = "pgo-default" +const ( + OPERATOR_SERVICE_ACCOUNT = "postgres-operator" + PGO_DEFAULT_SERVICE_ACCOUNT = "pgo-default" +) -const PGO_TARGET_ROLE = "pgo-target-role" -const PGO_TARGET_ROLE_BINDING = "pgo-target-role-binding" -const PGO_TARGET_SERVICE_ACCOUNT = "pgo-target" +const ( + PGO_TARGET_ROLE = "pgo-target-role" + PGO_TARGET_ROLE_BINDING = "pgo-target-role-binding" + PGO_TARGET_SERVICE_ACCOUNT = "pgo-target" +) -const PGO_BACKREST_ROLE = "pgo-backrest-role" -const PGO_BACKREST_SERVICE_ACCOUNT = "pgo-backrest" -const PGO_BACKREST_ROLE_BINDING = "pgo-backrest-role-binding" +const ( + PGO_BACKREST_ROLE = "pgo-backrest-role" + PGO_BACKREST_SERVICE_ACCOUNT = "pgo-backrest" + PGO_BACKREST_ROLE_BINDING = "pgo-backrest-role-binding" +) -const PGO_PG_ROLE = "pgo-pg-role" -const PGO_PG_ROLE_BINDING = "pgo-pg-role-binding" -const PGO_PG_SERVICE_ACCOUNT = "pgo-pg" +const ( + PGO_PG_ROLE = "pgo-pg-role" + PGO_PG_ROLE_BINDING = "pgo-pg-role-binding" + PGO_PG_SERVICE_ACCOUNT = "pgo-pg" +) // PgoServiceAccount is used to populate the following ServiceAccount templates: // pgo-default-sa.json @@ -135,7 +143,6 @@ var ( // CreateFakeNamespaceClient creates a fake namespace client for use with the "disabled" namespace // operating mode func CreateFakeNamespaceClient(installationName string) (kubernetes.Interface, error) { - var namespaces []runtime.Object for _, namespace := range getNamespacesFromEnv() { namespaces = append(namespaces, &v1.Namespace{ @@ -161,7 +168,7 @@ func CreateNamespace(clientset kubernetes.Interface, installationName, pgoNamesp log.Debugf("CreateNamespace %s %s %s", pgoNamespace, createdBy, newNs) - //define the new namespace + // define the new namespace n := v1.Namespace{} n.ObjectMeta.Labels = make(map[string]string) n.ObjectMeta.Labels[config.LABEL_VENDOR] = config.LABEL_CRUNCHY @@ -177,7 +184,7 @@ func CreateNamespace(clientset kubernetes.Interface, installationName, pgoNamesp log.Debugf("CreateNamespace %s created by %s", newNs, createdBy) - //publish event + // publish event topics := make([]string, 1) topics[0] = events.EventTopicPGO @@ -206,7 +213,7 @@ func DeleteNamespace(clientset kubernetes.Interface, installationName, pgoNamesp log.Debugf("DeleteNamespace %s deleted by %s", ns, deletedBy) - //publish the namespace delete event + // publish the namespace delete event topics := make([]string, 1) topics[0] = events.EventTopicPGO @@ -441,7 +448,7 @@ func UpdateNamespace(clientset kubernetes.Interface, installationName, pgoNamesp return err } - //publish event + // publish event topics := make([]string, 1) topics[0] = events.EventTopicPGO @@ -567,7 +574,6 @@ func GetCurrentNamespaceList(clientset kubernetes.Interface, func ValidateNamespacesWatched(clientset kubernetes.Interface, namespaceOperatingMode NamespaceOperatingMode, installationName string, namespaces ...string) error { - var err error var currNSList []string if namespaceOperatingMode != NamespaceOperatingModeDisabled { @@ -640,7 +646,6 @@ func ValidateNamespaceNames(namespace ...string) error { // (please see the various NamespaceOperatingMode types for a detailed explanation of each // operating mode). func GetNamespaceOperatingMode(clientset kubernetes.Interface) (NamespaceOperatingMode, error) { - // first check to see if dynamic namespace capabilities can be enabled isDynamic, err := CheckAccessPrivs(clientset, namespacePrivsCoreDynamic, "", "") if err != nil { @@ -710,7 +715,6 @@ func CheckAccessPrivs(clientset kubernetes.Interface, func GetInitialNamespaceList(clientset kubernetes.Interface, namespaceOperatingMode NamespaceOperatingMode, installationName, pgoNamespace string) ([]string, error) { - // next grab the namespaces provided using the NAMESPACE env var namespaceList := getNamespacesFromEnv() diff --git a/internal/operator/backrest/backup.go b/internal/operator/backrest/backup.go index 8d3cdeba4a..41a03a3d89 100644 --- a/internal/operator/backrest/backup.go +++ b/internal/operator/backrest/backup.go @@ -110,7 +110,7 @@ func Backrest(namespace string, clientset kubernetes.Interface, task *crv1.Pgtas } if operator.CRUNCHY_DEBUG { - config.BackrestjobTemplate.Execute(os.Stdout, jobFields) + _ = config.BackrestjobTemplate.Execute(os.Stdout, jobFields) } newjob := v1batch.Job{} @@ -131,7 +131,7 @@ func Backrest(namespace string, clientset kubernetes.Interface, task *crv1.Pgtas if backupType != "" { newjob.ObjectMeta.Labels[config.LABEL_PGHA_BACKUP_TYPE] = backupType } - clientset.BatchV1().Jobs(namespace).Create(ctx, &newjob, metav1.CreateOptions{}) + _, _ = clientset.BatchV1().Jobs(namespace).Create(ctx, &newjob, metav1.CreateOptions{}) // publish backrest backup event if cmd == "backup" { @@ -160,8 +160,7 @@ func Backrest(namespace string, clientset kubernetes.Interface, task *crv1.Pgtas // CreateInitialBackup creates a Pgtask in order to initiate the initial pgBackRest backup for a cluster // as needed to support replica creation func CreateInitialBackup(clientset pgo.Interface, namespace, clusterName, podName string) (*crv1.Pgtask, error) { - var params map[string]string - params = make(map[string]string) + params := make(map[string]string) params[config.LABEL_PGHA_BACKUP_TYPE] = crv1.BackupTypeBootstrap return CreateBackup(clientset, namespace, clusterName, podName, params, "--type=full") } @@ -169,8 +168,7 @@ func CreateInitialBackup(clientset pgo.Interface, namespace, clusterName, podNam // CreatePostFailoverBackup creates a Pgtask in order to initiate the a pgBackRest backup following a failure // event to ensure proper replica creation and/or reinitialization func CreatePostFailoverBackup(clientset pgo.Interface, namespace, clusterName, podName string) (*crv1.Pgtask, error) { - var params map[string]string - params = make(map[string]string) + params := make(map[string]string) params[config.LABEL_PGHA_BACKUP_TYPE] = crv1.BackupTypeFailover return CreateBackup(clientset, namespace, clusterName, podName, params, "") } diff --git a/internal/operator/backrest/repo.go b/internal/operator/backrest/repo.go index 6266afd510..988b279548 100644 --- a/internal/operator/backrest/repo.go +++ b/internal/operator/backrest/repo.go @@ -97,7 +97,7 @@ func CreateRepoDeployment(clientset kubernetes.Interface, cluster *crv1.Pgcluste serviceName = fmt.Sprintf(util.BackrestRepoServiceName, cluster.Name) } - //create backrest repo service + // create backrest repo service serviceFields := RepoServiceTemplateFields{ Name: serviceName, ClusterName: cluster.Name, @@ -135,7 +135,7 @@ func CreateRepoDeployment(clientset kubernetes.Interface, cluster *crv1.Pgcluste } if operator.CRUNCHY_DEBUG { - config.PgoBackrestRepoTemplate.Execute(os.Stdout, repoFields) + _ = config.PgoBackrestRepoTemplate.Execute(os.Stdout, repoFields) } deployment := appsv1.Deployment{} @@ -225,7 +225,6 @@ func setBootstrapRepoOverrides(clientset kubernetes.Interface, cluster *crv1.Pgc // specific PostgreSQL cluster. func getRepoDeploymentFields(clientset kubernetes.Interface, cluster *crv1.Pgcluster, replicas int) *RepoDeploymentTemplateFields { - namespace := cluster.GetNamespace() repoFields := RepoDeploymentTemplateFields{ @@ -265,7 +264,6 @@ func UpdateAnnotations(clientset kubernetes.Interface, cluster *crv1.Pgcluster, // get a list of all of the instance deployments for the cluster deployment, err := operator.GetBackrestDeployment(clientset, cluster) - if err != nil { return err } @@ -291,7 +289,6 @@ func UpdateResources(clientset kubernetes.Interface, cluster *crv1.Pgcluster) er // get a list of all of the instance deployments for the cluster deployment, err := operator.GetBackrestDeployment(clientset, cluster) - if err != nil { return err } @@ -333,7 +330,7 @@ func createService(clientset kubernetes.Interface, fields *RepoServiceTemplateFi } if operator.CRUNCHY_DEBUG { - config.PgoBackrestRepoServiceTemplate.Execute(os.Stdout, fields) + _ = config.PgoBackrestRepoServiceTemplate.Execute(os.Stdout, fields) } s := v1.Service{} diff --git a/internal/operator/backrest/restore.go b/internal/operator/backrest/restore.go index 1f23802deb..5d727522f6 100644 --- a/internal/operator/backrest/restore.go +++ b/internal/operator/backrest/restore.go @@ -73,7 +73,6 @@ type BackrestRestoreJobTemplateFields struct { // perform a restore func UpdatePGClusterSpecForRestore(clientset kubeapi.Interface, cluster *crv1.Pgcluster, task *crv1.Pgtask) { - cluster.Spec.PGDataSource.RestoreFrom = cluster.GetName() restoreOpts := task.Spec.Parameters[config.LABEL_BACKREST_RESTORE_OPTS] @@ -250,8 +249,10 @@ func PrepareClusterForRestore(clientset kubeapi.Interface, cluster *crv1.Pgclust clusterName) // Delete the DCS and leader ConfigMaps. These will be recreated during the restore. - configMaps := []string{fmt.Sprintf("%s-config", clusterName), - fmt.Sprintf("%s-leader", clusterName)} + configMaps := []string{ + fmt.Sprintf("%s-config", clusterName), + fmt.Sprintf("%s-leader", clusterName), + } for _, c := range configMaps { if err := clientset.CoreV1().ConfigMaps(namespace). Delete(ctx, c, metav1.DeleteOptions{}); err != nil && !kerrors.IsNotFound(err) { @@ -281,7 +282,7 @@ func PrepareClusterForRestore(clientset kubeapi.Interface, cluster *crv1.Pgclust func UpdateWorkflow(clientset pgo.Interface, workflowID, namespace, status string) error { ctx := context.TODO() - //update workflow + // update workflow log.Debugf("restore workflow: update workflow %s", workflowID) selector := crv1.PgtaskWorkflowID + "=" + workflowID taskList, err := clientset.CrunchydataV1().Pgtasks(namespace).List(ctx, metav1.ListOptions{LabelSelector: selector}) @@ -324,7 +325,6 @@ func PublishRestore(id, clusterName, username, namespace string) { if err != nil { log.Error(err.Error()) } - } // getPGDatabasePVCNames returns the names of all PostgreSQL database PVCs for a specific diff --git a/internal/operator/backrest/stanza.go b/internal/operator/backrest/stanza.go index 2607eb7a6e..a2a0176452 100644 --- a/internal/operator/backrest/stanza.go +++ b/internal/operator/backrest/stanza.go @@ -58,7 +58,7 @@ func StanzaCreate(namespace, clusterName string, clientset kubeapi.Interface) { ctx := context.TODO() taskName := clusterName + "-" + crv1.PgtaskBackrestStanzaCreate - //look up the backrest-repo pod name + // look up the backrest-repo pod name selector := config.LABEL_PG_CLUSTER + "=" + clusterName + "," + config.LABEL_PGO_BACKREST_REPO + "=true" pods, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{LabelSelector: selector}) if len(pods.Items) != 1 { @@ -78,7 +78,7 @@ func StanzaCreate(namespace, clusterName string, clientset kubeapi.Interface) { return } - //create the stanza-create task + // create the stanza-create task spec := crv1.PgtaskSpec{} spec.Name = taskName @@ -133,5 +133,4 @@ func StanzaCreate(namespace, clusterName string, clientset kubeapi.Interface) { if err != nil { log.Error(err) } - } diff --git a/internal/operator/cluster/cluster.go b/internal/operator/cluster/cluster.go index 09d91ce454..464e1bd28e 100644 --- a/internal/operator/cluster/cluster.go +++ b/internal/operator/cluster/cluster.go @@ -156,8 +156,8 @@ func AddClusterBase(clientset kubeapi.Interface, cl *crv1.Pgcluster, namespace s log.Error("error in pvcname patch " + err.Error()) } - //publish create cluster event - //capture the cluster creation event + // publish create cluster event + // capture the cluster creation event pgouser := cl.ObjectMeta.Labels[config.LABEL_PGOUSER] topics := make([]string, 1) topics[0] = events.EventTopicCluster @@ -190,15 +190,15 @@ func AddClusterBase(clientset kubeapi.Interface, cl *crv1.Pgcluster, namespace s publishClusterCreateFailure(cl, err.Error()) return } - //create a CRD for each replica + // create a CRD for each replica for i := 0; i < replicaCount; i++ { spec := crv1.PgreplicaSpec{} - //get the storage config + // get the storage config spec.ReplicaStorage = cl.Spec.ReplicaStorage spec.UserLabels = cl.Spec.UserLabels - //the replica should not use the same node labels as the primary + // the replica should not use the same node labels as the primary spec.UserLabels[config.LABEL_NODE_LABEL_KEY] = "" spec.UserLabels[config.LABEL_NODE_LABEL_VALUE] = "" @@ -324,17 +324,16 @@ func AddBootstrapRepo(clientset kubernetes.Interface, cluster *crv1.Pgcluster) ( // DeleteClusterBase ... func DeleteClusterBase(clientset kubernetes.Interface, cl *crv1.Pgcluster, namespace string) { + _ = DeleteCluster(clientset, cl, namespace) - DeleteCluster(clientset, cl, namespace) - - //delete any existing configmaps + // delete any existing configmaps if err := deleteConfigMaps(clientset, cl.Spec.Name, namespace); err != nil { log.Error(err) } - //delete any existing pgtasks ??? + // delete any existing pgtasks ??? - //publish delete cluster event + // publish delete cluster event topics := make([]string, 1) topics[0] = events.EventTopicCluster @@ -363,7 +362,7 @@ func ScaleBase(clientset kubeapi.Interface, replica *crv1.Pgreplica, namespace s return } - //get the pgcluster CRD to base the replica off of + // get the pgcluster CRD to base the replica off of cluster, err := clientset.CrunchydataV1().Pgclusters(namespace). Get(ctx, replica.Spec.ClusterName, metav1.GetOptions{}) if err != nil { @@ -378,7 +377,7 @@ func ScaleBase(clientset kubeapi.Interface, replica *crv1.Pgreplica, namespace s return } - //update the replica CRD pvcname + // update the replica CRD pvcname patch, err := kubeapi.NewJSONPatch().Add("spec", "replicastorage", "name")(dataVolume.PersistentVolumeClaimName).Bytes() if err == nil { log.Debugf("patching replica %s: %s", replica.Spec.Name, patch) @@ -389,20 +388,20 @@ func ScaleBase(clientset kubeapi.Interface, replica *crv1.Pgreplica, namespace s log.Error("error in pvcname patch " + err.Error()) } - //create the replica service if it doesnt exist + // create the replica service if it doesnt exist if err = scaleReplicaCreateMissingService(clientset, replica, cluster, namespace); err != nil { log.Error(err) publishScaleError(namespace, replica.ObjectMeta.Labels[config.LABEL_PGOUSER], cluster) return } - //instantiate the replica + // instantiate the replica if err = scaleReplicaCreateDeployment(clientset, replica, cluster, namespace, dataVolume, walVolume, tablespaceVolumes); err != nil { publishScaleError(namespace, replica.ObjectMeta.Labels[config.LABEL_PGOUSER], cluster) return } - //update the replica CRD status + // update the replica CRD status patch, err = kubeapi.NewJSONPatch().Add("spec", "status")(crv1.CompletedStatus).Bytes() if err == nil { log.Debugf("patching replica %s: %s", replica.Spec.Name, patch) @@ -413,7 +412,7 @@ func ScaleBase(clientset kubeapi.Interface, replica *crv1.Pgreplica, namespace s log.Error("error in status patch " + err.Error()) } - //publish event for replica creation + // publish event for replica creation topics := make([]string, 1) topics[0] = events.EventTopicCluster @@ -438,16 +437,16 @@ func ScaleBase(clientset kubeapi.Interface, replica *crv1.Pgreplica, namespace s func ScaleDownBase(clientset kubeapi.Interface, replica *crv1.Pgreplica, namespace string) { ctx := context.TODO() - //get the pgcluster CRD for this replica + // get the pgcluster CRD for this replica _, err := clientset.CrunchydataV1().Pgclusters(namespace). Get(ctx, replica.Spec.ClusterName, metav1.GetOptions{}) if err != nil { return } - DeleteReplica(clientset, replica, namespace) + _ = DeleteReplica(clientset, replica, namespace) - //publish event for scale down + // publish event for scale down topics := make([]string, 1) topics[0] = events.EventTopicCluster @@ -467,7 +466,6 @@ func ScaleDownBase(clientset kubeapi.Interface, replica *crv1.Pgreplica, namespa log.Error(err.Error()) return } - } // UpdateAnnotations updates the annotations in the "template" portion of a @@ -693,10 +691,9 @@ func publishClusterCreateFailure(cl *crv1.Pgcluster, errorMsg string) { } func publishClusterShutdown(cluster crv1.Pgcluster) error { - clusterName := cluster.Name - //capture the cluster creation event + // capture the cluster creation event topics := make([]string, 1) topics[0] = events.EventTopicCluster diff --git a/internal/operator/cluster/clusterlogic.go b/internal/operator/cluster/clusterlogic.go index c464da161b..3af8b13729 100644 --- a/internal/operator/cluster/clusterlogic.go +++ b/internal/operator/cluster/clusterlogic.go @@ -86,7 +86,7 @@ func addClusterBootstrapJob(clientset kubeapi.Interface, tablespaceVolumes map[string]operator.StorageResult) error { ctx := context.TODO() - bootstrapFields, err := getBootstrapJobFields(clientset, cl, dataVolume, walVolume, + bootstrapFields, err := getBootstrapJobFields(clientset, cl, dataVolume, tablespaceVolumes) if err != nil { return err @@ -98,7 +98,7 @@ func addClusterBootstrapJob(clientset kubeapi.Interface, } if operator.CRUNCHY_DEBUG { - config.DeploymentTemplate.Execute(os.Stdout, bootstrapFields) + _ = config.DeploymentTemplate.Execute(os.Stdout, bootstrapFields) } job := &batchv1.Job{} @@ -134,7 +134,7 @@ func addClusterDeployments(clientset kubeapi.Interface, } deploymentFields := getClusterDeploymentFields(clientset, cl, - dataVolume, walVolume, tablespaceVolumes) + dataVolume, tablespaceVolumes) var primaryDoc bytes.Buffer if err := config.DeploymentTemplate.Execute(&primaryDoc, deploymentFields); err != nil { @@ -142,7 +142,7 @@ func addClusterDeployments(clientset kubeapi.Interface, } if operator.CRUNCHY_DEBUG { - config.DeploymentTemplate.Execute(os.Stdout, deploymentFields) + _ = config.DeploymentTemplate.Execute(os.Stdout, deploymentFields) } deployment := &appsv1.Deployment{} @@ -170,7 +170,7 @@ func addClusterDeployments(clientset kubeapi.Interface, // getBootstrapJobFields obtains the fields needed to populate the cluster bootstrap job template func getBootstrapJobFields(clientset kubeapi.Interface, - cluster *crv1.Pgcluster, dataVolume, walVolume operator.StorageResult, + cluster *crv1.Pgcluster, dataVolume operator.StorageResult, tablespaceVolumes map[string]operator.StorageResult) (operator.BootstrapJobTemplateFields, error) { ctx := context.TODO() @@ -179,7 +179,7 @@ func getBootstrapJobFields(clientset kubeapi.Interface, bootstrapFields := operator.BootstrapJobTemplateFields{ DeploymentTemplateFields: getClusterDeploymentFields(clientset, cluster, dataVolume, - walVolume, tablespaceVolumes), + tablespaceVolumes), RestoreFrom: cluster.Spec.PGDataSource.RestoreFrom, RestoreOpts: restoreOpts[1 : len(restoreOpts)-1], } @@ -250,7 +250,7 @@ func getBootstrapJobFields(clientset kubeapi.Interface, // getClusterDeploymentFields obtains the fields needed to populate the cluster deployment template func getClusterDeploymentFields(clientset kubernetes.Interface, - cl *crv1.Pgcluster, dataVolume, walVolume operator.StorageResult, + cl *crv1.Pgcluster, dataVolume operator.StorageResult, tablespaceVolumes map[string]operator.StorageResult) operator.DeploymentTemplateFields { namespace := cl.GetNamespace() @@ -362,7 +362,7 @@ func DeleteCluster(clientset kubernetes.Interface, cl *crv1.Pgcluster, namespace log.Error(err) return err } else { - publishDeleteCluster(namespace, cl.ObjectMeta.Labels[config.LABEL_PGOUSER], cl.Spec.Name, cl.ObjectMeta.Labels[config.LABEL_PG_CLUSTER_IDENTIFIER]) + publishDeleteCluster(namespace, cl.ObjectMeta.Labels[config.LABEL_PGOUSER], cl.Spec.Name) } return err @@ -513,7 +513,7 @@ func scaleReplicaCreateDeployment(clientset kubernetes.Interface, } if operator.CRUNCHY_DEBUG { - config.DeploymentTemplate.Execute(os.Stdout, replicaDeploymentFields) + _ = config.DeploymentTemplate.Execute(os.Stdout, replicaDeploymentFields) } replicaDeployment := appsv1.Deployment{} @@ -581,7 +581,7 @@ func publishScaleError(namespace string, username string, cluster *crv1.Pgcluste } } -func publishDeleteCluster(namespace, username, clusterName, identifier string) { +func publishDeleteCluster(namespace, username, clusterName string) { topics := make([]string, 1) topics[0] = events.EventTopicCluster @@ -691,7 +691,7 @@ func ShutdownCluster(clientset kubeapi.Interface, cluster crv1.Pgcluster) error return err } - publishClusterShutdown(cluster) + _ = publishClusterShutdown(cluster) return nil } diff --git a/internal/operator/cluster/common.go b/internal/operator/cluster/common.go index ebdc5adfec..82bf11c3de 100644 --- a/internal/operator/cluster/common.go +++ b/internal/operator/cluster/common.go @@ -83,6 +83,7 @@ func generatePassword() (string, error) { // makePostgreSQLPassword creates the expected hash for a password type for a // PostgreSQL password +// nolint:unparam // this is set up to accept SCRAM in the not-too-distant future func makePostgreSQLPassword(passwordType pgpassword.PasswordType, username, password string) string { // get the PostgreSQL password generate based on the password type // as all of these values are valid, this not not error diff --git a/internal/operator/cluster/failover.go b/internal/operator/cluster/failover.go index 5f64b86f08..5c2b43e173 100644 --- a/internal/operator/cluster/failover.go +++ b/internal/operator/cluster/failover.go @@ -40,9 +40,9 @@ func FailoverBase(namespace string, clientset kubeapi.Interface, task *crv1.Pgta ctx := context.TODO() var err error - //look up the pgcluster for this task - //in the case, the clustername is passed as a key in the - //parameters map + // look up the pgcluster for this task + // in the case, the clustername is passed as a key in the + // parameters map var clusterName string for k := range task.Spec.Parameters { clusterName = k @@ -53,14 +53,14 @@ func FailoverBase(namespace string, clientset kubeapi.Interface, task *crv1.Pgta return } - //create marker (clustername, namespace) + // create marker (clustername, namespace) err = PatchpgtaskFailoverStatus(clientset, task, namespace) if err != nil { log.Errorf("could not set failover started marker for task %s cluster %s", task.Spec.Name, clusterName) return } - //get initial count of replicas --selector=pg-cluster=clusterName + // get initial count of replicas --selector=pg-cluster=clusterName selector := config.LABEL_PG_CLUSTER + "=" + clusterName replicaList, err := clientset.CrunchydataV1().Pgreplicas(namespace).List(ctx, metav1.ListOptions{LabelSelector: selector}) if err != nil { @@ -69,7 +69,7 @@ func FailoverBase(namespace string, clientset kubeapi.Interface, task *crv1.Pgta } log.Debugf("replica count before failover is %d", len(replicaList.Items)) - //publish event for failover + // publish event for failover topics := make([]string, 1) topics[0] = events.EventTopicCluster @@ -90,9 +90,9 @@ func FailoverBase(namespace string, clientset kubeapi.Interface, task *crv1.Pgta log.Error(err) } - Failover(cluster.ObjectMeta.Labels[config.LABEL_PG_CLUSTER_IDENTIFIER], clientset, clusterName, task, namespace, restconfig) + _ = Failover(cluster.ObjectMeta.Labels[config.LABEL_PG_CLUSTER_IDENTIFIER], clientset, clusterName, task, namespace, restconfig) - //publish event for failover completed + // publish event for failover completed topics = make([]string, 1) topics[0] = events.EventTopicCluster @@ -113,17 +113,16 @@ func FailoverBase(namespace string, clientset kubeapi.Interface, task *crv1.Pgta log.Error(err) } - //remove marker - + // remove marker } func PatchpgtaskFailoverStatus(clientset pgo.Interface, oldCrd *crv1.Pgtask, namespace string) error { ctx := context.TODO() - //change it + // change it oldCrd.Spec.Parameters[config.LABEL_FAILOVER_STARTED] = time.Now().Format(time.RFC3339) - //create the patch + // create the patch patchBytes, err := json.Marshal(map[string]interface{}{ "spec": map[string]interface{}{ "parameters": oldCrd.Spec.Parameters, @@ -133,10 +132,9 @@ func PatchpgtaskFailoverStatus(clientset pgo.Interface, oldCrd *crv1.Pgtask, nam return err } - //apply patch + // apply patch _, err6 := clientset.CrunchydataV1().Pgtasks(namespace). Patch(ctx, oldCrd.Name, types.MergePatchType, patchBytes, metav1.PatchOptions{}) return err6 - } diff --git a/internal/operator/cluster/failoverlogic.go b/internal/operator/cluster/failoverlogic.go index 4ffa67e4d4..f1ec1183d6 100644 --- a/internal/operator/cluster/failoverlogic.go +++ b/internal/operator/cluster/failoverlogic.go @@ -54,19 +54,19 @@ func Failover(identifier string, clientset kubeapi.Interface, clusterName string } log.Debugf("pod selected to failover to is %s", pod.Name) - updateFailoverStatus(clientset, task, namespace, clusterName, "deleted primary deployment "+clusterName) + updateFailoverStatus(clientset, task, namespace, "deleted primary deployment "+clusterName) - //trigger the failover to the selected replica + // trigger the failover to the selected replica if err := promote(pod, clientset, namespace, restconfig); err != nil { log.Warn(err) } - publishPromoteEvent(identifier, namespace, task.ObjectMeta.Labels[config.LABEL_PGOUSER], clusterName, target) + publishPromoteEvent(namespace, task.ObjectMeta.Labels[config.LABEL_PGOUSER], clusterName, target) - updateFailoverStatus(clientset, task, namespace, clusterName, "promoting pod "+pod.Name+" target "+target) + updateFailoverStatus(clientset, task, namespace, "promoting pod "+pod.Name+" target "+target) - //relabel the deployment with primary labels - //by setting service-name=clustername + // relabel the deployment with primary labels + // by setting service-name=clustername upod, err := clientset.CoreV1().Pods(namespace).Get(ctx, pod.Name, metav1.GetOptions{}) if err != nil { log.Error(err) @@ -74,8 +74,8 @@ func Failover(identifier string, clientset kubeapi.Interface, clusterName string return err } - //set the service-name label to the cluster name to match - //the primary service selector + // set the service-name label to the cluster name to match + // the primary service selector log.Debugf("setting label on pod %s=%s", config.LABEL_SERVICE_NAME, clusterName) patch, err := kubeapi.NewMergePatch().Add("metadata", "labels", config.LABEL_SERVICE_NAME)(clusterName).Bytes() @@ -100,9 +100,9 @@ func Failover(identifier string, clientset kubeapi.Interface, clusterName string return err } - updateFailoverStatus(clientset, task, namespace, clusterName, "updating label deployment...pod "+pod.Name+"was the failover target...failover completed") + updateFailoverStatus(clientset, task, namespace, "updating label deployment...pod "+pod.Name+"was the failover target...failover completed") - //update the pgcluster current-primary to new deployment name + // update the pgcluster current-primary to new deployment name cluster, err := clientset.CrunchydataV1().Pgclusters(namespace).Get(ctx, clusterName, metav1.GetOptions{}) if err != nil { log.Errorf("could not find pgcluster %s with labels", clusterName) @@ -117,15 +117,14 @@ func Failover(identifier string, clientset kubeapi.Interface, clusterName string } return nil - } -func updateFailoverStatus(clientset pgo.Interface, task *crv1.Pgtask, namespace, clusterName, message string) { +func updateFailoverStatus(clientset pgo.Interface, task *crv1.Pgtask, namespace, message string) { ctx := context.TODO() log.Debugf("updateFailoverStatus namespace=[%s] taskName=[%s] message=[%s]", namespace, task.Name, message) - //update the task + // update the task t, err := clientset.CrunchydataV1().Pgtasks(task.Namespace).Get(ctx, task.Name, metav1.GetOptions{}) if err != nil { return @@ -139,14 +138,12 @@ func updateFailoverStatus(clientset pgo.Interface, task *crv1.Pgtask, namespace, return } *task = *t - } func promote( pod *v1.Pod, clientset kubernetes.Interface, namespace string, restconfig *rest.Config) error { - // generate the curl command that will be run on the pod selected for the failover in order // to trigger the failover and promote that specific pod to primary command := make([]string, 3) @@ -165,7 +162,7 @@ func promote( return err } -func publishPromoteEvent(identifier, namespace, username, clusterName, target string) { +func publishPromoteEvent(namespace, username, clusterName, target string) { topics := make([]string, 1) topics[0] = events.EventTopicCluster @@ -185,7 +182,6 @@ func publishPromoteEvent(identifier, namespace, username, clusterName, target st if err != nil { log.Error(err.Error()) } - } // RemovePrimaryOnRoleChangeTag sets the 'primary_on_role_change' tag to null in the diff --git a/internal/operator/cluster/pgadmin.go b/internal/operator/cluster/pgadmin.go index 529bba6f13..9ed1bdbea5 100644 --- a/internal/operator/cluster/pgadmin.go +++ b/internal/operator/cluster/pgadmin.go @@ -346,6 +346,7 @@ func createPgAdminDeployment(clientset kubernetes.Interface, cluster *crv1.Pgclu // This password is throwaway so low entropy genreation method is fine randBytes := make([]byte, initPassLen) // weakrand Read is always nil error + // #nosec: G404 weakrand.Read(randBytes) throwawayPass := base64.RawStdEncoding.EncodeToString(randBytes) @@ -364,7 +365,7 @@ func createPgAdminDeployment(clientset kubernetes.Interface, cluster *crv1.Pgclu // For debugging purposes, put the template substitution in stdout if operator.CRUNCHY_DEBUG { - config.PgAdminTemplate.Execute(os.Stdout, fields) + _ = config.PgAdminTemplate.Execute(os.Stdout, fields) } // perform the actual template substitution @@ -409,7 +410,7 @@ func createPgAdminService(clientset kubernetes.Interface, cluster *crv1.Pgcluste // For debugging purposes, put the template substitution in stdout if operator.CRUNCHY_DEBUG { - config.PgAdminServiceTemplate.Execute(os.Stdout, fields) + _ = config.PgAdminServiceTemplate.Execute(os.Stdout, fields) } // perform the actual template substitution diff --git a/internal/operator/cluster/pgbouncer.go b/internal/operator/cluster/pgbouncer.go index 0a78e305f2..7ee7487bd4 100644 --- a/internal/operator/cluster/pgbouncer.go +++ b/internal/operator/cluster/pgbouncer.go @@ -557,7 +557,7 @@ func createPgBouncerDeployment(clientset kubernetes.Interface, cluster *crv1.Pgc // For debugging purposes, put the template substitution in stdout if operator.CRUNCHY_DEBUG { - config.PgbouncerTemplate.Execute(os.Stdout, fields) + _ = config.PgbouncerTemplate.Execute(os.Stdout, fields) } // perform the actual template substitution diff --git a/internal/operator/cluster/rmdata.go b/internal/operator/cluster/rmdata.go index d82405fd18..27c224eaec 100644 --- a/internal/operator/cluster/rmdata.go +++ b/internal/operator/cluster/rmdata.go @@ -72,7 +72,7 @@ func CreateRmdataJob(clientset kubernetes.Interface, cl *crv1.Pgcluster, namespa } if operator.CRUNCHY_DEBUG { - config.RmdatajobTemplate.Execute(os.Stdout, jobFields) + _ = config.RmdatajobTemplate.Execute(os.Stdout, jobFields) } newjob := v1batch.Job{} diff --git a/internal/operator/cluster/service.go b/internal/operator/cluster/service.go index d2438f77c5..bca0eb9e45 100644 --- a/internal/operator/cluster/service.go +++ b/internal/operator/cluster/service.go @@ -37,7 +37,7 @@ func CreateService(clientset kubernetes.Interface, fields *ServiceTemplateFields ctx := context.TODO() var serviceDoc bytes.Buffer - //create the service if it doesn't exist + // create the service if it doesn't exist _, err := clientset.CoreV1().Services(namespace).Get(ctx, fields.Name, metav1.GetOptions{}) if err != nil { @@ -48,7 +48,7 @@ func CreateService(clientset kubernetes.Interface, fields *ServiceTemplateFields } if operator.CRUNCHY_DEBUG { - config.ServiceTemplate.Execute(os.Stdout, fields) + _ = config.ServiceTemplate.Execute(os.Stdout, fields) } service := corev1.Service{} @@ -62,5 +62,4 @@ func CreateService(clientset kubernetes.Interface, fields *ServiceTemplateFields } return err - } diff --git a/internal/operator/cluster/standby.go b/internal/operator/cluster/standby.go index 30bcc7edbe..0ec759e025 100644 --- a/internal/operator/cluster/standby.go +++ b/internal/operator/cluster/standby.go @@ -189,10 +189,10 @@ func EnableStandby(clientset kubernetes.Interface, cluster crv1.Pgcluster) error // grab the json stored in the config annotation configJSONStr := dcsConfigMap.ObjectMeta.Annotations["config"] var configJSON map[string]interface{} - json.Unmarshal([]byte(configJSONStr), &configJSON) + _ = json.Unmarshal([]byte(configJSONStr), &configJSON) var standbyJSON map[string]interface{} - json.Unmarshal([]byte(standbyClusterConfigJSON), &standbyJSON) + _ = json.Unmarshal([]byte(standbyClusterConfigJSON), &standbyJSON) // set standby_cluster to default config unless already set if _, ok := configJSON["standby_cluster"]; !ok { @@ -244,10 +244,9 @@ func EnableStandby(clientset kubernetes.Interface, cluster crv1.Pgcluster) error } func publishStandbyEnabled(cluster *crv1.Pgcluster) error { - clusterName := cluster.Name - //capture the cluster creation event + // capture the cluster creation event topics := make([]string, 1) topics[0] = events.EventTopicCluster diff --git a/internal/operator/cluster/upgrade.go b/internal/operator/cluster/upgrade.go index ecbd9d9985..4bb6d63a53 100644 --- a/internal/operator/cluster/upgrade.go +++ b/internal/operator/cluster/upgrade.go @@ -77,7 +77,7 @@ func AddUpgrade(clientset kubeapi.Interface, upgrade *crv1.Pgtask, namespace str } // update the workflow status to 'in progress' while the upgrade takes place - updateUpgradeWorkflow(clientset, namespace, upgrade.ObjectMeta.Labels[crv1.PgtaskWorkflowID], crv1.PgtaskUpgradeInProgress) + _ = updateUpgradeWorkflow(clientset, namespace, upgrade.ObjectMeta.Labels[crv1.PgtaskWorkflowID], crv1.PgtaskUpgradeInProgress) // grab the existing pgo version oldpgoversion := pgcluster.ObjectMeta.Labels[config.LABEL_PGO_VERSION] @@ -100,10 +100,10 @@ func AddUpgrade(clientset kubeapi.Interface, upgrade *crv1.Pgtask, namespace str SetReplicaNumber(pgcluster, replicas) // create the 'pgha-config' configmap while taking the init value from any existing 'pgha-default-config' configmap - createUpgradePGHAConfigMap(clientset, pgcluster, namespace) + _ = createUpgradePGHAConfigMap(clientset, pgcluster, namespace) // delete the existing pgcluster CRDs and other resources that will be recreated - deleteBeforeUpgrade(clientset, pgcluster.Name, currentPrimary, namespace, pgcluster.Spec.Standby) + deleteBeforeUpgrade(clientset, pgcluster.Name, currentPrimary, namespace) // recreate new Backrest Repo secret that was just deleted recreateBackrestRepoSecret(clientset, upgradeTargetClusterName, namespace, operator.PgoNamespace) @@ -222,14 +222,14 @@ func handleReplicas(clientset kubeapi.Interface, clusterName, currentPrimaryPVC, log.Debugf("scaling down pgreplica: %s", replicaList.Items[index].Name) ScaleDownBase(clientset, &replicaList.Items[index], namespace) log.Debugf("deleting pgreplica CRD: %s", replicaList.Items[index].Name) - clientset.CrunchydataV1().Pgreplicas(namespace).Delete(ctx, replicaList.Items[index].Name, metav1.DeleteOptions{}) + _ = clientset.CrunchydataV1().Pgreplicas(namespace).Delete(ctx, replicaList.Items[index].Name, metav1.DeleteOptions{}) // if the existing replica PVC is not being used as the primary PVC, delete // note this will not remove any leftover PVCs from previous failovers, // those will require manual deletion so as to avoid any accidental // deletion of valid PVCs. if replicaList.Items[index].Name != currentPrimaryPVC { deletePropagation := metav1.DeletePropagationForeground - clientset. + _ = clientset. CoreV1().PersistentVolumeClaims(namespace). Delete(ctx, replicaList.Items[index].Name, metav1.DeleteOptions{PropagationPolicy: &deletePropagation}) log.Debugf("deleting replica pvc: %s", replicaList.Items[index].Name) @@ -256,7 +256,7 @@ func SetReplicaNumber(pgcluster *crv1.Pgcluster, numReplicas string) { // deleteBeforeUpgrade deletes the deployments, services, pgcluster, jobs, tasks and default configmaps before attempting // to upgrade the pgcluster deployment. This preserves existing secrets, non-standard configmaps and service definitions // for use in the newly upgraded cluster. -func deleteBeforeUpgrade(clientset kubeapi.Interface, clusterName, currentPrimary, namespace string, isStandby bool) { +func deleteBeforeUpgrade(clientset kubeapi.Interface, clusterName, currentPrimary, namespace string) { ctx := context.TODO() // first, get all deployments for the pgcluster in question @@ -287,11 +287,11 @@ func deleteBeforeUpgrade(clientset kubeapi.Interface, clusterName, currentPrimar log.Debug(waitStatus) // delete the pgcluster - clientset.CrunchydataV1().Pgclusters(namespace).Delete(ctx, clusterName, metav1.DeleteOptions{}) + _ = clientset.CrunchydataV1().Pgclusters(namespace).Delete(ctx, clusterName, metav1.DeleteOptions{}) // delete all existing job references deletePropagation := metav1.DeletePropagationForeground - clientset. + _ = clientset. BatchV1().Jobs(namespace). DeleteCollection(ctx, metav1.DeleteOptions{PropagationPolicy: &deletePropagation}, @@ -307,11 +307,11 @@ func deleteBeforeUpgrade(clientset kubeapi.Interface, clusterName, currentPrimar // delete the leader configmap used by the Postgres Operator since this information may change after // the upgrade is complete // Note: deletion is required for cluster recreation - clientset.CoreV1().ConfigMaps(namespace).Delete(ctx, clusterName+"-leader", metav1.DeleteOptions{}) + _ = clientset.CoreV1().ConfigMaps(namespace).Delete(ctx, clusterName+"-leader", metav1.DeleteOptions{}) // delete the '-pgha-default-config' configmap, if it exists so the config syncer // will not try to use it instead of '-pgha-config' - clientset.CoreV1().ConfigMaps(namespace).Delete(ctx, clusterName+"-pgha-default-config", metav1.DeleteOptions{}) + _ = clientset.CoreV1().ConfigMaps(namespace).Delete(ctx, clusterName+"-pgha-default-config", metav1.DeleteOptions{}) } // deploymentWait is modified from cluster.waitForDeploymentDelete. It simply waits for the current primary deployment diff --git a/internal/operator/clusterutilities.go b/internal/operator/clusterutilities.go index 030e706404..da27c3c614 100644 --- a/internal/operator/clusterutilities.go +++ b/internal/operator/clusterutilities.go @@ -37,8 +37,10 @@ import ( ) // consolidate with cluster.affinityTemplateFields -const AffinityInOperator = "In" -const AFFINITY_NOTINOperator = "NotIn" +const ( + AffinityInOperator = "In" + AFFINITY_NOTINOperator = "NotIn" +) // PGHAConfigMapSuffix defines the suffix for the name of the PGHA configMap created for each PG // cluster @@ -99,7 +101,7 @@ type exporterTemplateFields struct { TLSOnly bool } -//consolidate +// consolidate type badgerTemplateFields struct { CCPImageTag string CCPImagePrefix string @@ -253,6 +255,7 @@ func GetAnnotations(cluster *crv1.Pgcluster, annotationType crv1.ClusterAnnotati for k, v := range cluster.Spec.Annotations.Postgres { annotations[k] = v } + case crv1.ClusterAnnotationGlobal: // no-op as its handled in the loop above } // if the map is empty, return an empty string @@ -262,7 +265,6 @@ func GetAnnotations(cluster *crv1.Pgcluster, annotationType crv1.ClusterAnnotati // let's try to create a JSON document out of the above doc, err := json.Marshal(annotations) - // if there is an error, warn in our logs and return an empty string if err != nil { log.Errorf("could not set custom annotations: %q", err) @@ -272,7 +274,7 @@ func GetAnnotations(cluster *crv1.Pgcluster, annotationType crv1.ClusterAnnotati return string(doc) } -//consolidate with cluster.GetPgbackrestEnvVars +// consolidate with cluster.GetPgbackrestEnvVars func GetPgbackrestEnvVars(cluster *crv1.Pgcluster, backrestEnabled, depName, port, storageType string) string { if backrestEnabled == "true" { fields := PgbackrestEnvVarsTemplateFields{ @@ -294,14 +296,12 @@ func GetPgbackrestEnvVars(cluster *crv1.Pgcluster, backrestEnabled, depName, por return doc.String() } return "" - } // GetPgbackrestBootstrapEnvVars returns a string containing the pgBackRest environment variables // for a bootstrap job func GetPgbackrestBootstrapEnvVars(restoreClusterName, depName string, restoreFromSecret *v1.Secret) (string, error) { - fields := PgbackrestEnvVarsTemplateFields{ PgbackrestStanza: "db", PgbackrestDBPath: fmt.Sprintf("/pgdata/%s", depName), @@ -331,7 +331,6 @@ func GetBackrestDeployment(clientset kubernetes.Interface, cluster *crv1.Pgclust } func GetBadgerAddon(clientset kubernetes.Interface, namespace string, cluster *crv1.Pgcluster, pgbadger_target string) string { - spec := cluster.Spec if cluster.Labels[config.LABEL_BADGER] == "true" { @@ -350,7 +349,7 @@ func GetBadgerAddon(clientset kubernetes.Interface, namespace string, cluster *c } if CRUNCHY_DEBUG { - config.BadgerTemplate.Execute(os.Stdout, badgerTemplateFields) + _ = config.BadgerTemplate.Execute(os.Stdout, badgerTemplateFields) } return badgerDoc.String() } @@ -396,16 +395,16 @@ func GetExporterAddon(spec crv1.PgclusterSpec) string { return exporterDoc.String() } -//consolidate with cluster.GetConfVolume +// consolidate with cluster.GetConfVolume func GetConfVolume(clientset kubernetes.Interface, cl *crv1.Pgcluster, namespace string) string { ctx := context.TODO() var configMapStr string - //check for user provided configmap + // check for user provided configmap if cl.Spec.CustomConfig != "" { _, err := clientset.CoreV1().ConfigMaps(namespace).Get(ctx, cl.Spec.CustomConfig, metav1.GetOptions{}) if err != nil { - //you should NOT get this error because of apiserver validation of this value! + // you should NOT get this error because of apiserver validation of this value! log.Errorf("%s was not found, error, skipping user provided configMap", cl.Spec.CustomConfig) } else { log.Debugf("user provided configmap %s was used for this cluster", cl.Spec.CustomConfig) @@ -413,7 +412,7 @@ func GetConfVolume(clientset kubernetes.Interface, cl *crv1.Pgcluster, namespace } } - //check for global custom configmap "pgo-custom-pg-config" + // check for global custom configmap "pgo-custom-pg-config" _, err := clientset.CoreV1().ConfigMaps(namespace).Get(ctx, config.GLOBAL_CUSTOM_CONFIGMAP, metav1.GetOptions{}) if err == nil { return `"pgo-custom-pg-config"` @@ -494,7 +493,6 @@ func GetInstanceDeployments(clientset kubernetes.Interface, cluster *crv1.Pgclus clusterDeployments, err := clientset. AppsV1().Deployments(cluster.Namespace). List(ctx, metav1.ListOptions{LabelSelector: selector}) - if err != nil { return nil, err } @@ -653,7 +651,7 @@ func GetAffinity(nodeLabelKey, nodeLabelValue string, affoperator string) string } if CRUNCHY_DEBUG { - config.AffinityTemplate.Execute(os.Stdout, affinityTemplateFields) + _ = config.AffinityTemplate.Execute(os.Stdout, affinityTemplateFields) } return affinityDoc.String() @@ -662,7 +660,6 @@ func GetAffinity(nodeLabelKey, nodeLabelValue string, affoperator string) string // GetPodAntiAffinity returns the populated pod anti-affinity json that should be attached to // the various pods comprising the pg cluster func GetPodAntiAffinity(cluster *crv1.Pgcluster, deploymentType crv1.PodAntiAffinityDeployment, podAntiAffinityType crv1.PodAntiAffinityType) string { - log.Debugf("GetPodAnitAffinity with clusterName=[%s]", cluster.Spec.Name) // run through the checks on the pod anti-affinity type to see if it is not @@ -689,6 +686,7 @@ func GetPodAntiAffinity(cluster *crv1.Pgcluster, deploymentType crv1.PodAntiAffi return "" case crv1.PodAntiAffinityRequired: templateAffinityType = requireScheduleIgnoreExec + case crv1.PodAntiAffinityPreffered: // no-op as its the default value } podAntiAffinityTemplateFields := podAntiAffinityTemplateFields{ @@ -708,7 +706,7 @@ func GetPodAntiAffinity(cluster *crv1.Pgcluster, deploymentType crv1.PodAntiAffi } if CRUNCHY_DEBUG { - config.PodAntiAffinityTemplate.Execute(os.Stdout, podAntiAffinityTemplateFields) + _ = config.PodAntiAffinityTemplate.Execute(os.Stdout, podAntiAffinityTemplateFields) } return podAntiAffinityDoc.String() @@ -751,6 +749,7 @@ func GetPodAntiAffinityType(cluster *crv1.Pgcluster, deploymentType crv1.PodAnti return podAntiAffinityType } } + case crv1.PodAntiAffinityDeploymentDefault: // no-op as its the default setting } // check to see if the value for the cluster anti-affinity is set. If so, use @@ -795,7 +794,6 @@ func GetPgmonitorEnvVars(cluster *crv1.Pgcluster) string { // and pgBackRest deployments. func GetPgbackrestS3EnvVars(cluster crv1.Pgcluster, clientset kubernetes.Interface, ns string) string { - if !strings.Contains(cluster.Spec.UserLabels[config.LABEL_BACKREST_STORAGE_TYPE], "s3") { return "" } @@ -865,7 +863,6 @@ func GetPgbackrestS3EnvVars(cluster crv1.Pgcluster, clientset kubernetes.Interfa // option is used, then returns the pgBackRest S3 configuration value to either enable // or disable TLS verification as the expected string value. func GetS3VerifyTLSSetting(cluster *crv1.Pgcluster) string { - // If the pgcluster has already been set, either by the PGO client or from the // CRD definition, parse the boolean value given. // If this value is not set, then parse the value stored in the default @@ -890,7 +887,6 @@ func GetS3VerifyTLSSetting(cluster *crv1.Pgcluster) string { // for inclusion in the PG and pgBackRest deployments. func GetPgbackrestBootstrapS3EnvVars(pgDataSourceRestoreFrom string, restoreFromSecret *v1.Secret) string { - s3EnvVars := PgbackrestS3EnvVarsTemplateFields{ PgbackrestS3Key: util.BackRestRepoSecretKeyAWSS3KeyAWSS3Key, PgbackrestS3KeySecret: util.BackRestRepoSecretKeyAWSS3KeyAWSS3KeySecret, @@ -1010,7 +1006,6 @@ func OverrideClusterContainerImages(containers []v1.Container) { // into the current buffer func writeTablespaceJSON(w *bytes.Buffer, jsonFields interface{}) error { json, err := json.Marshal(jsonFields) - // if there is an error, log the error and continue if err != nil { return err diff --git a/internal/operator/clusterutilities_test.go b/internal/operator/clusterutilities_test.go index 081e5eab62..7a3643f777 100644 --- a/internal/operator/clusterutilities_test.go +++ b/internal/operator/clusterutilities_test.go @@ -123,7 +123,6 @@ func TestGetAnnotations(t *testing.T) { } func TestOverrideClusterContainerImages(t *testing.T) { - containerDefaults := map[string]struct { name string image string @@ -250,7 +249,6 @@ func TestOverrideClusterContainerImages(t *testing.T) { } func TestGetPgbackrestBootstrapS3EnvVars(t *testing.T) { - // create a fake client that will be used to "fake" the initialization of the operator for // this test fakePGOClient, err := fakekubeapi.NewFakePGOClient() @@ -283,7 +281,6 @@ func TestGetPgbackrestBootstrapS3EnvVars(t *testing.T) { // test all env vars are properly set according the contents of an existing pgBackRest // repo secret t.Run("populate from secret", func(t *testing.T) { - backRestRepoSecret := mockBackRestRepoSecret.DeepCopy() s3EnvVars := GetPgbackrestBootstrapS3EnvVars(defaultRestoreFromCluster, backRestRepoSecret) // massage the results a bit so that we can parse as proper JSON to validate contents @@ -332,7 +329,6 @@ func TestGetPgbackrestBootstrapS3EnvVars(t *testing.T) { // test that the proper default S3 URI style is set for the bootstrap S3 env vars when the // S3 URI style annotation is an empty string in a pgBackRest repo secret t.Run("default URI style", func(t *testing.T) { - // the expected default for the pgBackRest URI style defaultURIStyle := "host" diff --git a/internal/operator/common.go b/internal/operator/common.go index a3917dfc27..819fdc84d5 100644 --- a/internal/operator/common.go +++ b/internal/operator/common.go @@ -43,12 +43,16 @@ const ( defaultRegistry = "registry.developers.crunchydata.com/crunchydata" ) -var CRUNCHY_DEBUG bool -var NAMESPACE string +var ( + CRUNCHY_DEBUG bool + NAMESPACE string +) -var InstallationName string -var PgoNamespace string -var EventTCPAddress = "localhost:4150" +var ( + InstallationName string + PgoNamespace string + EventTCPAddress = "localhost:4150" +) var Pgo config.PgoConfig @@ -75,7 +79,6 @@ type containerResourcesTemplateFields struct { var defaultBackrestRepoConfigKeys = []string{"config", "sshd_config", "aws-s3-ca.crt"} func Initialize(clientset kubernetes.Interface) { - tmp := os.Getenv("CRUNCHY_DEBUG") if tmp == "true" { CRUNCHY_DEBUG = true @@ -170,7 +173,6 @@ func GetPodSecurityContext(supplementalGroups []int64) string { // ...convert to JSON. Errors are ignored doc, err := json.Marshal(securityContext) - // if there happens to be an error, warn about it if err != nil { log.Warn(err) @@ -223,7 +225,7 @@ func GetResourcesJSON(resources, limits v1.ResourceList) string { } if log.GetLevel() == log.DebugLevel { - config.ContainerResourcesTemplate.Execute(os.Stdout, fields) + _ = config.ContainerResourcesTemplate.Execute(os.Stdout, fields) } return doc.String() @@ -314,7 +316,6 @@ func initializeControllerRefreshIntervals() { // attempting to utilize the worker counts defined in the pgo.yaml config file, and if not // present then falling back to a default value. func initializeControllerWorkerCounts() { - if Pgo.Pgo.ConfigMapWorkerCount == nil { log.Debugf("ConfigMapWorkerCount not set, defaulting to %d worker(s)", config.DefaultConfigMapWorkerCount) @@ -377,8 +378,7 @@ func initializeOperatorBackrestSecret(clientset kubernetes.Interface, namespace secret, err := clientset. CoreV1().Secrets(namespace). Get(ctx, config.SecretOperatorBackrestRepoConfig, metav1.GetOptions{}) - - // if there is a true error, return. Otherwise, initialize a new Secret + // if there is a true error, return. Otherwise, initialize a new Secret if err != nil { if !kerrors.IsNotFound(err) { return err @@ -436,7 +436,6 @@ func initializeOperatorBackrestSecret(clientset kubernetes.Interface, namespace // namespaces as needed (or as permitted by the current operator mode), and returning a valid list // of namespaces for the current Operator install. func SetupNamespaces(clientset kubernetes.Interface) ([]string, error) { - // First set the proper namespace operating mode for the Operator install. The mode identified // determines whether or not certain namespace capabilities are enabled. if err := setNamespaceOperatingMode(clientset); err != nil { diff --git a/internal/operator/config/configutil.go b/internal/operator/config/configutil.go index 1cbefba2ac..9c11483dac 100644 --- a/internal/operator/config/configutil.go +++ b/internal/operator/config/configutil.go @@ -42,10 +42,8 @@ const ( pghLocalConfigSuffix = "-local-config" ) -var ( - // ErrMissingClusterConfig is the error thrown when configuration is missing from a configMap - ErrMissingClusterConfig error = errors.New("Configuration is missing from configMap") -) +// ErrMissingClusterConfig is the error thrown when configuration is missing from a configMap +var ErrMissingClusterConfig error = errors.New("Configuration is missing from configMap") // Syncer defines a resource that is able to sync its configuration stored configuration with a // service, application, etc. diff --git a/internal/operator/config/dcs.go b/internal/operator/config/dcs.go index dc7663acbc..fe405d05c1 100644 --- a/internal/operator/config/dcs.go +++ b/internal/operator/config/dcs.go @@ -99,7 +99,6 @@ type SlotDCS struct { // include a configMap that will be used to configure the DCS for a specific cluster. func NewDCS(configMap *corev1.ConfigMap, kubeclientset kubernetes.Interface, clusterScope string) *DCS { - clusterName := configMap.GetLabels()[config.LABEL_PG_CLUSTER] return &DCS{ @@ -114,7 +113,6 @@ func NewDCS(configMap *corev1.ConfigMap, kubeclientset kubernetes.Interface, // configuration is missing from the configMap, then and attempt is made to add it by refreshing // the DCS configuration. func (d *DCS) Sync() error { - clusterName := d.configMap.GetObjectMeta().GetLabels()[config.LABEL_PG_CLUSTER] namespace := d.configMap.GetObjectMeta().GetNamespace() @@ -123,7 +121,6 @@ func (d *DCS) Sync() error { if err := d.apply(); err != nil && errors.Is(err, ErrMissingClusterConfig) { - if err := d.refresh(); err != nil { return err } @@ -140,7 +137,6 @@ func (d *DCS) Sync() error { // Update updates the contents of the DCS configuration stored within the configMap included // in the DCS. func (d *DCS) Update(dcsConfig *DCSConfig) error { - clusterName := d.configMap.GetObjectMeta().GetLabels()[config.LABEL_PG_CLUSTER] namespace := d.configMap.GetObjectMeta().GetNamespace() @@ -167,7 +163,6 @@ func (d *DCS) Update(dcsConfig *DCSConfig) error { // "-config" configMap, with the contents of the "" // configuration included in the DCS's configMap. func (d *DCS) apply() error { - clusterName := d.configMap.GetLabels()[config.LABEL_PG_CLUSTER] namespace := d.configMap.GetObjectMeta().GetNamespace() @@ -250,7 +245,6 @@ func (d *DCS) getClusterDCSConfig() (*DCSConfig, map[string]json.RawMessage, err // configMap, i.e. the contents of the "" configuration unmarshalled // into a DCSConfig struct. func (d *DCS) GetDCSConfig() (*DCSConfig, map[string]json.RawMessage, error) { - dcsYAML, ok := d.configMap.Data[d.configName] if !ok { return nil, nil, ErrMissingClusterConfig @@ -291,7 +285,6 @@ func (d *DCS) patchDCSAnnotation(content string) error { // configMap with the current DCS configuration for the cluster. Specifically, it is updated with // the configuration stored in the "config" annotation of the "-config" configMap. func (d *DCS) refresh() error { - clusterName := d.configMap.Labels[config.LABEL_PG_CLUSTER] namespace := d.configMap.GetObjectMeta().GetNamespace() diff --git a/internal/operator/config/localdb.go b/internal/operator/config/localdb.go index d7eef19bf8..2e4a630563 100644 --- a/internal/operator/config/localdb.go +++ b/internal/operator/config/localdb.go @@ -38,7 +38,8 @@ import ( var ( // readConfigCMD is the command used to read local cluster configuration in a database // container - readConfigCMD []string = []string{"bash", "-c", + readConfigCMD []string = []string{ + "bash", "-c", "/opt/crunchy/bin/yq r /tmp/postgres-ha-bootstrap.yaml postgresql | " + "/opt/crunchy/bin/yq p - postgresql", } @@ -120,7 +121,6 @@ type CreateReplicaMethod struct { // servers. func NewLocalDB(configMap *corev1.ConfigMap, restConfig *rest.Config, kubeclientset kubernetes.Interface) (*LocalDB, error) { - clusterName := configMap.GetLabels()[config.LABEL_PG_CLUSTER] namespace := configMap.GetObjectMeta().GetNamespace() @@ -142,7 +142,6 @@ func NewLocalDB(configMap *corev1.ConfigMap, restConfig *rest.Config, // configMap, then and attempt is made to add it by refreshing that specific configuration. Also, any // configurations within the configMap associated with servers that no longer exist are removed. func (l *LocalDB) Sync() error { - clusterName := l.configMap.GetObjectMeta().GetLabels()[config.LABEL_PG_CLUSTER] namespace := l.configMap.GetObjectMeta().GetNamespace() @@ -156,7 +155,7 @@ func (l *LocalDB) Sync() error { // delete any configs that are in the configMap but don't have an associated DB server in the // cluster go func() { - l.clean() + _ = l.clean() wg.Done() }() @@ -166,11 +165,9 @@ func (l *LocalDB) Sync() error { wg.Add(1) go func(config string) { - // attempt to apply DCS config if err := l.apply(config); err != nil && errors.Is(err, ErrMissingClusterConfig) { - if err := l.refresh(config); err != nil { // log the error and move on log.Error(err) @@ -195,7 +192,6 @@ func (l *LocalDB) Sync() error { // Update updates the contents of the configuration for a specific database server in // the PG cluster, specifically within the configMap included in the LocalDB. func (l *LocalDB) Update(configName string, localDBConfig LocalDBConfig) error { - clusterName := l.configMap.GetObjectMeta().GetLabels()[config.LABEL_PG_CLUSTER] namespace := l.configMap.GetObjectMeta().GetNamespace() @@ -255,7 +251,6 @@ func (l *LocalDB) apply(configName string) error { stdout, stderr, err := kubeapi.ExecToPodThroughAPI(l.restConfig, l.kubeclientset, applyCommand, dbPod.Spec.Containers[0].Name, dbPod.GetName(), namespace, nil) - if err != nil { log.Error(stderr, stdout) return err @@ -271,7 +266,7 @@ func (l *LocalDB) apply(configName string) error { // LocalDB if the database server they are associated with no longer exists func (l *LocalDB) clean() error { ctx := context.TODO() - var patch = kubeapi.NewJSONPatch() + patch := kubeapi.NewJSONPatch() var cmlocalConfigs []string // first grab all current local configs from the configMap @@ -320,7 +315,6 @@ func (l *LocalDB) getLocalConfigFromCluster(configName string) (*LocalDBConfig, dbPodList, err := l.kubeclientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{ LabelSelector: selector, }) - if err != nil { return nil, err } @@ -353,7 +347,6 @@ func (l *LocalDB) getLocalConfigFromCluster(configName string) (*LocalDBConfig, // configMap for a specific database server, i.e. the contents of the "" // configuration unmarshalled into a LocalConfig struct. func (l *LocalDB) getLocalConfig(configName string) (string, error) { - localYAML, ok := l.configMap.Data[configName] if !ok { return "", ErrMissingClusterConfig @@ -379,7 +372,6 @@ func (l *LocalDB) getLocalConfig(configName string) (string, error) { // with the contents of the Patroni YAML configuration file stored in the container running the // server. func (l *LocalDB) refresh(configName string) error { - clusterName := l.configMap.GetObjectMeta().GetLabels()[config.LABEL_PG_CLUSTER] namespace := l.configMap.GetObjectMeta().GetNamespace() diff --git a/internal/operator/operatorupgrade/version-check.go b/internal/operator/operatorupgrade/version-check.go index dce4196634..8544c77b77 100644 --- a/internal/operator/operatorupgrade/version-check.go +++ b/internal/operator/operatorupgrade/version-check.go @@ -45,7 +45,8 @@ func CheckVersion(clientset pgo.Interface, ns string) error { } // where the Operator versions do not match, label the pgclusters accordingly - for _, cluster := range clusterList.Items { + for i := range clusterList.Items { + cluster := &clusterList.Items[i] if msgs.PGO_VERSION != cluster.Spec.UserLabels[config.LABEL_PGO_VERSION] { log.Infof("operator version check - pgcluster %s version is currently %s, current version is %s", cluster.Name, cluster.Spec.UserLabels[config.LABEL_PGO_VERSION], msgs.PGO_VERSION) // check if the annotations map has been created @@ -54,8 +55,7 @@ func CheckVersion(clientset pgo.Interface, ns string) error { cluster.Annotations = map[string]string{} } cluster.Annotations[config.ANNOTATION_IS_UPGRADED] = config.ANNOTATIONS_FALSE - _, err = clientset.CrunchydataV1().Pgclusters(ns).Update(ctx, &cluster, metav1.UpdateOptions{}) - if err != nil { + if _, err := clientset.CrunchydataV1().Pgclusters(ns).Update(ctx, cluster, metav1.UpdateOptions{}); err != nil { return fmt.Errorf("%s: %w", ErrUnsuccessfulVersionCheck, err) } } @@ -69,7 +69,8 @@ func CheckVersion(clientset pgo.Interface, ns string) error { } // where the Operator versions do not match, label the replicas accordingly - for _, replica := range replicaList.Items { + for i := range replicaList.Items { + replica := &replicaList.Items[i] if msgs.PGO_VERSION != replica.Spec.UserLabels[config.LABEL_PGO_VERSION] { log.Infof("operator version check - pgcluster replica %s version is currently %s, current version is %s", replica.Name, replica.Spec.UserLabels[config.LABEL_PGO_VERSION], msgs.PGO_VERSION) // check if the annotations map has been created @@ -78,8 +79,7 @@ func CheckVersion(clientset pgo.Interface, ns string) error { replica.Annotations = map[string]string{} } replica.Annotations[config.ANNOTATION_IS_UPGRADED] = config.ANNOTATIONS_FALSE - _, err = clientset.CrunchydataV1().Pgreplicas(ns).Update(ctx, &replica, metav1.UpdateOptions{}) - if err != nil { + if _, err := clientset.CrunchydataV1().Pgreplicas(ns).Update(ctx, replica, metav1.UpdateOptions{}); err != nil { return fmt.Errorf("%s: %w", ErrUnsuccessfulVersionCheck, err) } } diff --git a/internal/operator/pgbackrest.go b/internal/operator/pgbackrest.go index 42a8f645d1..8e369e764c 100644 --- a/internal/operator/pgbackrest.go +++ b/internal/operator/pgbackrest.go @@ -59,8 +59,8 @@ func addBackRestConfigDirectoryVolumeAndMounts(podSpec *v1.PodSpec, volumeName s // Any projections are included as custom pgBackRest configuration. func AddBackRestConfigVolumeAndMounts(podSpec *v1.PodSpec, clusterName string, projections []v1.VolumeProjection) { var combined []v1.VolumeProjection - var defaultConfigNames = clusterName + "-config-backrest" - var varTrue = true + defaultConfigNames := clusterName + "-config-backrest" + varTrue := true // Start with custom configurations from the CRD. combined = append(combined, projections...) diff --git a/internal/operator/pgdump/dump.go b/internal/operator/pgdump/dump.go index 060043d8c1..1df0efbc79 100644 --- a/internal/operator/pgdump/dump.go +++ b/internal/operator/pgdump/dump.go @@ -60,7 +60,7 @@ func Dump(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask) { ctx := context.TODO() var err error - //create the Job to run the pgdump command + // create the Job to run the pgdump command cmd := task.Spec.Parameters[config.LABEL_PGDUMP_COMMAND] @@ -128,7 +128,7 @@ func Dump(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask) { } if operator.CRUNCHY_DEBUG { - config.PgDumpBackupJobTemplate.Execute(os.Stdout, jobFields) + _ = config.PgDumpBackupJobTemplate.Execute(os.Stdout, jobFields) } newjob := v1batch.Job{} @@ -148,7 +148,7 @@ func Dump(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask) { return } - //update the pgdump task status to submitted - updates task, not the job. + // update the pgdump task status to submitted - updates task, not the job. patch, err := kubeapi.NewJSONPatch().Add("spec", "status")(crv1.PgBackupJobSubmitted).Bytes() if err == nil { log.Debugf("patching task %s: %s", task.Spec.Name, patch) @@ -158,5 +158,4 @@ func Dump(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask) { if err != nil { log.Error(err.Error()) } - } diff --git a/internal/operator/pgdump/restore.go b/internal/operator/pgdump/restore.go index 6d874f4a9e..95169c06de 100644 --- a/internal/operator/pgdump/restore.go +++ b/internal/operator/pgdump/restore.go @@ -72,7 +72,7 @@ func Restore(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask) { return } - //use the storage config from the primary PostgreSQL cluster + // use the storage config from the primary PostgreSQL cluster storage := cluster.Spec.PrimaryStorage taskName := task.Name @@ -104,7 +104,7 @@ func Restore(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask) { } if operator.CRUNCHY_DEBUG { - config.PgRestoreJobTemplate.Execute(os.Stdout, jobFields) + _ = config.PgRestoreJobTemplate.Execute(os.Stdout, jobFields) } newjob := v1batch.Job{} @@ -125,5 +125,4 @@ func Restore(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask) { return } log.Debugf("pgrestore job %s created", j.Name) - } diff --git a/internal/operator/pvc/pvc.go b/internal/operator/pvc/pvc.go index 24304b895a..21d2fc2808 100644 --- a/internal/operator/pvc/pvc.go +++ b/internal/operator/pvc/pvc.go @@ -152,7 +152,7 @@ func Create(clientset kubernetes.Interface, name, clusterName string, storageSpe log.Debug("using dynamic PVC template") err = config.PVCStorageClassTemplate.Execute(&doc2, pvcFields) if operator.CRUNCHY_DEBUG { - config.PVCStorageClassTemplate.Execute(os.Stdout, pvcFields) + _ = config.PVCStorageClassTemplate.Execute(os.Stdout, pvcFields) } } else { log.Debugf("matchlabels from spec is [%s]", storageSpec.MatchLabels) @@ -168,7 +168,7 @@ func Create(clientset kubernetes.Interface, name, clusterName string, storageSpe err = config.PVCTemplate.Execute(&doc2, pvcFields) if operator.CRUNCHY_DEBUG { - config.PVCTemplate.Execute(os.Stdout, pvcFields) + _ = config.PVCTemplate.Execute(os.Stdout, pvcFields) } } if err != nil { @@ -217,7 +217,6 @@ func Exists(clientset kubernetes.Interface, name string, namespace string) bool } func getMatchLabels(key, value string) string { - matchLabelsTemplateFields := matchLabelsTemplateFields{} matchLabelsTemplateFields.Key = key matchLabelsTemplateFields.Value = value @@ -230,5 +229,4 @@ func getMatchLabels(key, value string) string { } return doc.String() - } diff --git a/internal/operator/storage.go b/internal/operator/storage.go index da06087deb..83b3918ae7 100644 --- a/internal/operator/storage.go +++ b/internal/operator/storage.go @@ -33,7 +33,7 @@ func (s StorageResult) InlineVolumeSource() string { b := new(bytes.Buffer) e := json.NewEncoder(b) e.SetEscapeHTML(false) - e.Encode(s.VolumeSource()) + _ = e.Encode(s.VolumeSource()) // remove trailing newline and surrounding brackets return b.String()[1 : b.Len()-2] diff --git a/internal/operator/storage_test.go b/internal/operator/storage_test.go index 280b1c6cd0..44a235fff0 100644 --- a/internal/operator/storage_test.go +++ b/internal/operator/storage_test.go @@ -32,10 +32,14 @@ func TestStorageResultInlineVolumeSource(t *testing.T) { expected string }{ {StorageResult{}, `"emptyDir":{}`}, - {StorageResult{PersistentVolumeClaimName: "<\x00"}, - `"persistentVolumeClaim":{"claimName":"<\u0000"}`}, - {StorageResult{PersistentVolumeClaimName: "some-name"}, - `"persistentVolumeClaim":{"claimName":"some-name"}`}, + { + StorageResult{PersistentVolumeClaimName: "<\x00"}, + `"persistentVolumeClaim":{"claimName":"<\u0000"}`, + }, + { + StorageResult{PersistentVolumeClaimName: "some-name"}, + `"persistentVolumeClaim":{"claimName":"some-name"}`, + }, } { if actual := tt.value.InlineVolumeSource(); actual != tt.expected { t.Errorf("expected %q for %v, got %q", tt.expected, tt.value, actual) diff --git a/internal/operator/task/applypolicies.go b/internal/operator/task/applypolicies.go index c3d1d306c9..11b568f0c4 100644 --- a/internal/operator/task/applypolicies.go +++ b/internal/operator/task/applypolicies.go @@ -33,13 +33,13 @@ func ApplyPolicies(clusterName string, clientset kubeapi.Interface, RESTConfig * task, err := clientset.CrunchydataV1().Pgtasks(ns).Get(ctx, taskName, metav1.GetOptions{}) if err == nil { - //apply those policies + // apply those policies for k := range task.Spec.Parameters { log.Debugf("applying policy %s to %s", k, clusterName) applyPolicy(clientset, RESTConfig, k, clusterName, ns) } - //delete the pgtask to not redo this again - clientset.CrunchydataV1().Pgtasks(ns).Delete(ctx, taskName, metav1.DeleteOptions{}) + // delete the pgtask to not redo this again + _ = clientset.CrunchydataV1().Pgtasks(ns).Delete(ctx, taskName, metav1.DeleteOptions{}) } } @@ -70,11 +70,10 @@ func applyPolicy(clientset kubeapi.Interface, restconfig *rest.Config, policyNam log.Error(err) } - //update the pgcluster crd labels with the new policy + // update the pgcluster crd labels with the new policy log.Debugf("patching cluster %s: %s", cl.Spec.Name, patch) _, err = clientset.CrunchydataV1().Pgclusters(ns).Patch(ctx, cl.Spec.Name, types.MergePatchType, patch, metav1.PatchOptions{}) if err != nil { log.Error(err) } - } diff --git a/internal/operator/task/rmbackups.go b/internal/operator/task/rmbackups.go deleted file mode 100644 index 91f48ee2e6..0000000000 --- a/internal/operator/task/rmbackups.go +++ /dev/null @@ -1,42 +0,0 @@ -package task - -/* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. - 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. -*/ - -import ( - "context" - - "github.com/crunchydata/postgres-operator/internal/config" - crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" - log "github.com/sirupsen/logrus" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" -) - -// RemoveBackups ... -func RemoveBackups(namespace string, clientset kubernetes.Interface, task *crv1.Pgtask) { - ctx := context.TODO() - - //delete any backup jobs for this cluster - //kubectl delete job --selector=pg-cluster=clustername - - log.Debugf("deleting backup jobs with selector=%s=%s", config.LABEL_PG_CLUSTER, task.Spec.Parameters[config.LABEL_PG_CLUSTER]) - deletePropagation := metav1.DeletePropagationForeground - clientset. - BatchV1().Jobs(namespace). - DeleteCollection(ctx, - metav1.DeleteOptions{PropagationPolicy: &deletePropagation}, - metav1.ListOptions{LabelSelector: config.LABEL_PG_CLUSTER + "=" + task.Spec.Parameters[config.LABEL_PG_CLUSTER]}) -} diff --git a/internal/operator/task/rmdata.go b/internal/operator/task/rmdata.go index b44c529b4b..eb9c9c2fe8 100644 --- a/internal/operator/task/rmdata.go +++ b/internal/operator/task/rmdata.go @@ -53,7 +53,7 @@ type rmdatajobTemplateFields struct { func RemoveData(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask) { ctx := context.TODO() - //create marker (clustername, namespace) + // create marker (clustername, namespace) patch, err := kubeapi.NewJSONPatch(). Add("spec", "parameters", config.LABEL_DELETE_DATA_STARTED)(time.Now().Format(time.RFC3339)). Bytes() @@ -67,8 +67,8 @@ func RemoveData(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask return } - //create the Job to remove the data - //pvcName := task.Spec.Parameters[config.LABEL_PVC_NAME] + // create the Job to remove the data + // pvcName := task.Spec.Parameters[config.LABEL_PVC_NAME] clusterName := task.Spec.Parameters[config.LABEL_PG_CLUSTER] clusterPGHAScope := task.Spec.Parameters[config.LABEL_PGHA_SCOPE] replicaName := task.Spec.Parameters[config.LABEL_REPLICA_NAME] @@ -116,7 +116,7 @@ func RemoveData(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask } if operator.CRUNCHY_DEBUG { - config.RmdatajobTemplate.Execute(os.Stdout, jobFields) + _ = config.RmdatajobTemplate.Execute(os.Stdout, jobFields) } newjob := v1batch.Job{} @@ -137,11 +137,11 @@ func RemoveData(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask } log.Debugf("successfully created rmdata job %s", j.Name) - publishDeleteCluster(task.Spec.Parameters[config.LABEL_PG_CLUSTER], task.ObjectMeta.Labels[config.LABEL_PG_CLUSTER_IDENTIFIER], + publishDeleteCluster(task.Spec.Parameters[config.LABEL_PG_CLUSTER], task.ObjectMeta.Labels[config.LABEL_PGOUSER], namespace) } -func publishDeleteCluster(clusterName, identifier, username, namespace string) { +func publishDeleteCluster(clusterName, username, namespace string) { topics := make([]string, 1) topics[0] = events.EventTopicCluster diff --git a/internal/operator/task/workflow.go b/internal/operator/task/workflow.go index 43c6e1100b..531e3e5eac 100644 --- a/internal/operator/task/workflow.go +++ b/internal/operator/task/workflow.go @@ -30,19 +30,15 @@ import ( // CompleteCreateClusterWorkflow ... update the pgtask for the // create cluster workflow for a given cluster func CompleteCreateClusterWorkflow(clusterName string, clientset pgo.Interface, ns string) { - taskName := clusterName + "-" + crv1.PgtaskWorkflowCreateClusterType completeWorkflow(clientset, ns, taskName) - } func CompleteBackupWorkflow(clusterName string, clientset pgo.Interface, ns string) { - taskName := clusterName + "-" + crv1.PgtaskWorkflowBackupType completeWorkflow(clientset, ns, taskName) - } func completeWorkflow(clientset pgo.Interface, taskNamespace, taskName string) { @@ -54,7 +50,7 @@ func completeWorkflow(clientset pgo.Interface, taskNamespace, taskName string) { return } - //mark this workflow as completed + // mark this workflow as completed id := task.Spec.Parameters[crv1.PgtaskWorkflowID] log.Debugf("completing workflow %s id %s", taskName, id) @@ -72,5 +68,4 @@ func completeWorkflow(clientset pgo.Interface, taskNamespace, taskName string) { if err != nil { log.Error(err) } - } diff --git a/internal/patroni/patroni.go b/internal/patroni/patroni.go index 50d75bec33..111515c02a 100644 --- a/internal/patroni/patroni.go +++ b/internal/patroni/patroni.go @@ -37,12 +37,16 @@ const dbContainerName = "database" var ( // reloadCMD is the command for reloading a specific PG instance (primary or replica) within a // PG cluster - reloadCMD = []string{"/bin/bash", "-c", - fmt.Sprintf("curl -X POST --silent http://127.0.0.1:%s/reload", config.DEFAULT_PATRONI_PORT)} + reloadCMD = []string{ + "/bin/bash", "-c", + fmt.Sprintf("curl -X POST --silent http://127.0.0.1:%s/reload", config.DEFAULT_PATRONI_PORT), + } // restartCMD is the command for restart a specific PG database (primary or replica) within a // PG cluster - restartCMD = []string{"/bin/bash", "-c", - fmt.Sprintf("curl -X POST --silent http://127.0.0.1:%s/restart", config.DEFAULT_PATRONI_PORT)} + restartCMD = []string{ + "/bin/bash", "-c", + fmt.Sprintf("curl -X POST --silent http://127.0.0.1:%s/restart", config.DEFAULT_PATRONI_PORT), + } // ErrInstanceNotFound is the error thrown when a target instance cannot be found in the cluster ErrInstanceNotFound = errors.New("The instance does not exist in the cluster") @@ -77,7 +81,6 @@ type RestartResult struct { // NewPatroniClient creates a new Patroni client func NewPatroniClient(restConfig *rest.Config, kubeclientset kubernetes.Interface, clusterName, namespace string) Client { - return &patroniClient{ restConfig: restConfig, kubeclientset: kubeclientset, @@ -112,7 +115,6 @@ func (p *patroniClient) getClusterInstances() (map[string]corev1.Pod, error) { // ReloadCluster reloads the configuration for a PostgreSQL cluster. Specififcally, a Patroni // reload (which includes a PG reload) is executed on the primary and each replica within the cluster. func (p *patroniClient) ReloadCluster() error { - instanceMap, err := p.getClusterInstances() if err != nil { return err @@ -131,7 +133,6 @@ func (p *patroniClient) ReloadCluster() error { // Patroni restart is executed on the primary and each replica within the cluster. A slice is also // returned containing the names of all instances restarted within the cluster. func (p *patroniClient) RestartCluster() ([]RestartResult, error) { - var restartResult []RestartResult instanceMap, err := p.getClusterInstances() @@ -156,7 +157,6 @@ func (p *patroniClient) RestartCluster() ([]RestartResult, error) { // RestartInstances restarts the PostgreSQL databases for the instances specified. Specififcally, a // Patroni restart is executed on the primary and each replica within the cluster. func (p *patroniClient) RestartInstances(instances ...string) ([]RestartResult, error) { - var restartResult []RestartResult instanceMap, err := p.getClusterInstances() @@ -195,7 +195,6 @@ func (p *patroniClient) RestartInstances(instances ...string) ([]RestartResult, // reload performs a Patroni reload (which includes a PG reload) on a specific instance (primary or // replica) within a PG cluster func (p *patroniClient) reload(podName string) error { - stdout, stderr, err := kubeapi.ExecToPodThroughAPI(p.restConfig, p.kubeclientset, reloadCMD, dbContainerName, podName, p.namespace, nil) if err != nil { @@ -212,7 +211,6 @@ func (p *patroniClient) reload(podName string) error { // restart performs a Patroni restart on a specific instance (primary or replica) within a PG // cluster. func (p *patroniClient) restart(podName string) error { - stdout, stderr, err := kubeapi.ExecToPodThroughAPI(p.restConfig, p.kubeclientset, restartCMD, dbContainerName, podName, p.namespace, nil) if err != nil { diff --git a/internal/pgadmin/backoff.go b/internal/pgadmin/backoff.go index d1df68c80d..40cacaec13 100644 --- a/internal/pgadmin/backoff.go +++ b/internal/pgadmin/backoff.go @@ -40,6 +40,7 @@ const ( ) // Apply provides a new time with respect to t based on the jitter mode +// #nosec: G404 func (jm Jitter) Apply(t time.Duration) time.Duration { switch jm { case JitterNone: // being explicit in case default case changes diff --git a/internal/pgadmin/backoff_test.go b/internal/pgadmin/backoff_test.go index aeae16f7a5..09c0445526 100644 --- a/internal/pgadmin/backoff_test.go +++ b/internal/pgadmin/backoff_test.go @@ -108,7 +108,6 @@ func TestSubscripts(t *testing.T) { t.Fail() } } - } func TestUniformPolicy(t *testing.T) { diff --git a/internal/pgadmin/crypto_test.go b/internal/pgadmin/crypto_test.go index 36f8468379..221b23fb80 100644 --- a/internal/pgadmin/crypto_test.go +++ b/internal/pgadmin/crypto_test.go @@ -28,8 +28,10 @@ var testData = struct { clearPW: "w052H0UBM783B$x6N___", encPW: "5PN+lp8XXalwRzCptI21hmT5S9FvvEYpD8chWa39akY6Srwl", key: "$pbkdf2-sha512$19000$knLuvReC8H7v/T8n5JwTwg$OsVGpDa/zpCE2pKEOsZ4/SqdxcQZ0UU6v41ev/gkk4ROsrws/4I03oHqN37k.v1d25QckESs3NlPxIUv5gTf2Q", - iv: []byte{0xe4, 0xf3, 0x7e, 0x96, 0x9f, 0x17, 0x5d, 0xa9, - 0x70, 0x47, 0x30, 0xa9, 0xb4, 0x8d, 0xb5, 0x86}, + iv: []byte{ + 0xe4, 0xf3, 0x7e, 0x96, 0x9f, 0x17, 0x5d, 0xa9, + 0x70, 0x47, 0x30, 0xa9, 0xb4, 0x8d, 0xb5, 0x86, + }, } func TestSymmetry(t *testing.T) { diff --git a/internal/pgadmin/hash.go b/internal/pgadmin/hash.go index b73222fb8b..728faed22b 100644 --- a/internal/pgadmin/hash.go +++ b/internal/pgadmin/hash.go @@ -54,7 +54,7 @@ func HashPassword(qr *queryRunner, pass string) (string, error) { // Generate a "new" password derived from the provided password // Satisfies OWASP sec. 2.4.5: 'provide additional iteration of a key derivation' mac := hmac.New(sha512.New, saltBytes) - mac.Write([]byte(pass)) + _, _ = mac.Write([]byte(pass)) macBytes := mac.Sum(nil) macBase64 := base64.StdEncoding.EncodeToString(macBytes) diff --git a/internal/pgadmin/runner.go b/internal/pgadmin/runner.go index 233dc092be..7ce80a484c 100644 --- a/internal/pgadmin/runner.go +++ b/internal/pgadmin/runner.go @@ -102,7 +102,7 @@ func (qr *queryRunner) EnsureReady() error { cmd, qr.Pod.Spec.Containers[0].Name, qr.Pod.Name, qr.Namespace, nil) if err != nil && !strings.Contains(stderr, "no such table") { - lastError = fmt.Errorf("%v - %v", err, stderr) + lastError = fmt.Errorf("%w - %v", err, stderr) nextRoundIn := qr.BackoffPolicy.Duration(i) log.Debugf("[InitWait attempt %02d]: %v - retry in %v", i, err, nextRoundIn) time.Sleep(nextRoundIn) @@ -121,7 +121,7 @@ func (qr *queryRunner) EnsureReady() error { } } if lastError != nil && output == "" { - return fmt.Errorf("error executing query: %v", lastError) + return fmt.Errorf("error executing query: %w", lastError) } return nil @@ -141,7 +141,7 @@ func (qr *queryRunner) Exec(query string) error { _, stderr, err := kubeapi.ExecToPodThroughAPI(qr.apicfg, qr.clientset, cmd, qr.Pod.Spec.Containers[0].Name, qr.Pod.Name, qr.Namespace, nil) if err != nil { - lastError = fmt.Errorf("%v - %v", err, stderr) + lastError = fmt.Errorf("%w - %v", err, stderr) nextRoundIn := qr.BackoffPolicy.Duration(i) log.Debugf("[Exec attempt %02d]: %v - retry in %v", i, err, nextRoundIn) time.Sleep(nextRoundIn) @@ -151,7 +151,7 @@ func (qr *queryRunner) Exec(query string) error { } } if lastError != nil { - return fmt.Errorf("error executing query: %vv", lastError) + return fmt.Errorf("error executing query: %w", lastError) } return nil @@ -178,7 +178,7 @@ func (qr *queryRunner) Query(query string) (string, error) { stdout, stderr, err := kubeapi.ExecToPodThroughAPI(qr.apicfg, qr.clientset, cmd, qr.Pod.Spec.Containers[0].Name, qr.Pod.Name, qr.Namespace, nil) if err != nil { - lastError = fmt.Errorf("%v - %v", err, stderr) + lastError = fmt.Errorf("%w - %v", err, stderr) nextRoundIn := qr.BackoffPolicy.Duration(i) log.Debugf("[Query attempt %02d]: %v - retry in %v", i, err, nextRoundIn) time.Sleep(nextRoundIn) @@ -189,7 +189,7 @@ func (qr *queryRunner) Query(query string) (string, error) { } } if lastError != nil && output == "" { - return "", fmt.Errorf("error executing query: %v", lastError) + return "", fmt.Errorf("error executing query: %w", lastError) } return output, nil diff --git a/internal/postgres/password/md5.go b/internal/postgres/password/md5.go index 030fd21765..56f9504608 100644 --- a/internal/postgres/password/md5.go +++ b/internal/postgres/password/md5.go @@ -16,15 +16,14 @@ package password */ import ( + // #nosec G501 "crypto/md5" "errors" "fmt" ) -var ( - // ErrMD5PasswordInvalid is returned when the password attributes are invalid - ErrMD5PasswordInvalid = errors.New(`invalid password attributes. must provide "username" and "password"`) -) +// ErrMD5PasswordInvalid is returned when the password attributes are invalid +var ErrMD5PasswordInvalid = errors.New(`invalid password attributes. must provide "username" and "password"`) // MD5Password implements the PostgresPassword interface for hashing passwords // using the PostgreSQL MD5 method @@ -42,6 +41,7 @@ func (m *MD5Password) Build() (string, error) { plaintext := []byte(m.password + m.username) // finish the transformation by getting the string value of the MD5 hash and // encoding it in hexadecimal for PostgreSQL, appending "md5" to the front + // #nosec G401 return fmt.Sprintf("md5%x", md5.Sum(plaintext)), nil } diff --git a/internal/postgres/password/md5_test.go b/internal/postgres/password/md5_test.go index c77c8abf43..41c0711b04 100644 --- a/internal/postgres/password/md5_test.go +++ b/internal/postgres/password/md5_test.go @@ -38,7 +38,6 @@ func TestMD5Build(t *testing.T) { } hash, err := md5.Build() - if err != nil { t.Error(err) } diff --git a/internal/postgres/password/password.go b/internal/postgres/password/password.go index b70112a4c3..f63fb31492 100644 --- a/internal/postgres/password/password.go +++ b/internal/postgres/password/password.go @@ -31,10 +31,8 @@ const ( SCRAM ) -var ( - // ErrPasswordType is returned when a password type does not exist - ErrPasswordType = errors.New("password type does not exist") -) +// ErrPasswordType is returned when a password type does not exist +var ErrPasswordType = errors.New("password type does not exist") // PostgresPassword is the interface that defines the methods required to build // a password for PostgreSQL in a desired format (e.g. MD5) diff --git a/internal/postgres/password/password_test.go b/internal/postgres/password/password_test.go index b9b7094dbc..d315c966bc 100644 --- a/internal/postgres/password/password_test.go +++ b/internal/postgres/password/password_test.go @@ -16,6 +16,7 @@ package password */ import ( + "errors" "testing" ) @@ -27,7 +28,6 @@ func TestNewPostgresPassword(t *testing.T) { passwordType := MD5 postgresPassword, err := NewPostgresPassword(passwordType, username, password) - if err != nil { t.Error(err) } @@ -49,7 +49,6 @@ func TestNewPostgresPassword(t *testing.T) { passwordType := SCRAM postgresPassword, err := NewPostgresPassword(passwordType, username, password) - if err != nil { t.Error(err) } @@ -66,7 +65,7 @@ func TestNewPostgresPassword(t *testing.T) { t.Run("invalid", func(t *testing.T) { passwordType := PasswordType(-1) - if _, err := NewPostgresPassword(passwordType, username, password); err != ErrPasswordType { + if _, err := NewPostgresPassword(passwordType, username, password); !errors.Is(err, ErrPasswordType) { t.Errorf("expected error: %q", err.Error()) } }) diff --git a/internal/postgres/password/scram.go b/internal/postgres/password/scram.go index aa6eee3df8..794575e4dd 100644 --- a/internal/postgres/password/scram.go +++ b/internal/postgres/password/scram.go @@ -96,7 +96,6 @@ type SCRAMPassword struct { func (s *SCRAMPassword) Build() (string, error) { // get a generated salt salt, err := s.generateSalt(s.SaltLength) - if err != nil { return "", err } diff --git a/internal/postgres/password/scram_test.go b/internal/postgres/password/scram_test.go index 6de92bb17c..7cdac419d0 100644 --- a/internal/postgres/password/scram_test.go +++ b/internal/postgres/password/scram_test.go @@ -54,7 +54,6 @@ func TestScramGenerateSalt(t *testing.T) { for _, saltLength := range saltLengths { t.Run(fmt.Sprintf("salt length %d", saltLength), func(t *testing.T) { salt, err := scramGenerateSalt(saltLength) - if err != nil { t.Error(err) } @@ -71,7 +70,6 @@ func TestScramGenerateSalt(t *testing.T) { for _, saltLength := range saltLengths { t.Run(fmt.Sprintf("salt length %d", saltLength), func(t *testing.T) { - if _, err := scramGenerateSalt(saltLength); err == nil { t.Errorf("error expected for salt length of %d", saltLength) } @@ -82,7 +80,6 @@ func TestScramGenerateSalt(t *testing.T) { func TestSCRAMBuild(t *testing.T) { t.Run("scram-sha-256", func(t *testing.T) { - t.Run("valid", func(t *testing.T) { // check a few different password combinations. note: the salt is kept the // same so we can get a reproducible result @@ -104,7 +101,6 @@ func TestSCRAMBuild(t *testing.T) { scram.generateSalt = mockGenerateSalt hash, err := scram.Build() - if err != nil { t.Error(err) } @@ -152,7 +148,7 @@ func TestSCRAMHash(t *testing.T) { expected, _ := hex.DecodeString("877cc977e7b033e10d6e0b0d666da1f463bc51b1de48869250a0347ec1b2b8b3") actual := scram.hash(sha256.New, []byte("hippo")) - if bytes.Compare(expected, actual) != 0 { + if !bytes.Equal(expected, actual) { t.Errorf("expected: %x actual %x", expected, actual) } }) @@ -164,7 +160,7 @@ func TestSCRAMHMAC(t *testing.T) { expected, _ := hex.DecodeString("ac9872eb21043142c3bf073c9fa4caf9553940750ef7b85116905aaa456a2d07") actual := scram.hmac(sha256.New, []byte("hippo"), []byte("datalake")) - if bytes.Compare(expected, actual) != 0 { + if !bytes.Equal(expected, actual) { t.Errorf("expected: %x actual %x", expected, actual) } }) diff --git a/internal/tlsutil/primitives_test.go b/internal/tlsutil/primitives_test.go index 22676e9fbc..1c6538f543 100644 --- a/internal/tlsutil/primitives_test.go +++ b/internal/tlsutil/primitives_test.go @@ -17,6 +17,7 @@ limitations under the License. import ( "bytes" + "context" "crypto/rsa" "crypto/tls" "crypto/x509" @@ -93,8 +94,9 @@ func TestExtendedTrust(t *testing.T) { defer srv.Close() caTrust := x509.NewCertPool() - ExtendTrust(caTrust, bytes.NewReader(pemCert)) + _ = ExtendTrust(caTrust, bytes.NewReader(pemCert)) + // #nosec G402 srv.TLS = &tls.Config{ ServerName: "Stom", ClientAuth: tls.RequireAndVerifyClientCert, @@ -111,6 +113,7 @@ func TestExtendedTrust(t *testing.T) { } client := srv.Client() + // #nosec G402 client.Transport = &http.Transport{ TLSClientConfig: &tls.Config{ Certificates: []tls.Certificate{ @@ -122,7 +125,12 @@ func TestExtendedTrust(t *testing.T) { } // Confirm server response - res, err := client.Get(srv.URL) + req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, srv.URL, nil) + if err != nil { + t.Fatalf("error getting request - %s\n", err) + } + + res, err := client.Do(req) if err != nil { t.Fatalf("error getting response - %s\n", err) } diff --git a/internal/util/backrest.go b/internal/util/backrest.go index 66e3a2dec6..2c42d2ae4b 100644 --- a/internal/util/backrest.go +++ b/internal/util/backrest.go @@ -27,7 +27,8 @@ const ( BackrestRepoDeploymentName = "%s-backrest-shared-repo" BackrestRepoServiceName = "%s-backrest-shared-repo" BackrestRepoPVCName = "%s-pgbr-repo" - BackrestRepoSecretName = "%s-backrest-repo-config" + // #nosec: G101 + BackrestRepoSecretName = "%s-backrest-repo-config" ) // defines the default repo1-path for pgBackRest for use when a specic path is not provided @@ -42,7 +43,6 @@ const defaultBackrestRepoPath = "/backrestrepo/%s-backrest-shared-repo" // validation is ocurring for a restore, the ensure only one storage type is selected. func ValidateBackrestStorageTypeOnBackupRestore(newBackRestStorageType, currentBackRestStorageType string, restore bool) error { - if newBackRestStorageType != "" && !IsValidBackrestStorageType(newBackRestStorageType) { return fmt.Errorf("Invalid value provided for pgBackRest storage type. The following "+ "values are allowed: %s", "\""+strings.Join(crv1.BackrestStorageTypes, "\", \"")+"\"") diff --git a/internal/util/cluster.go b/internal/util/cluster.go index 03fbb3c69b..9bab736209 100644 --- a/internal/util/cluster.go +++ b/internal/util/cluster.go @@ -69,14 +69,20 @@ const ( const ( // three of these are exported, as they are used to help add the information // into the templates. Say the last one 10 times fast - BackRestRepoSecretKeyAWSS3KeyAWSS3CACert = "aws-s3-ca.crt" - BackRestRepoSecretKeyAWSS3KeyAWSS3Key = "aws-s3-key" + // #nosec: G101 + BackRestRepoSecretKeyAWSS3KeyAWSS3CACert = "aws-s3-ca.crt" + // #nosec: G101 + BackRestRepoSecretKeyAWSS3KeyAWSS3Key = "aws-s3-key" + // #nosec: G101 BackRestRepoSecretKeyAWSS3KeyAWSS3KeySecret = "aws-s3-key-secret" // the rest are private - backRestRepoSecretKeyAuthorizedKeys = "authorized_keys" - backRestRepoSecretKeySSHConfig = "config" - backRestRepoSecretKeySSHDConfig = "sshd_config" - backRestRepoSecretKeySSHPrivateKey = "id_ed25519" + backRestRepoSecretKeyAuthorizedKeys = "authorized_keys" + backRestRepoSecretKeySSHConfig = "config" + // #nosec: G101 + backRestRepoSecretKeySSHDConfig = "sshd_config" + // #nosec: G101 + backRestRepoSecretKeySSHPrivateKey = "id_ed25519" + // #nosec: G101 backRestRepoSecretKeySSHHostPrivateKey = "ssh_host_ed25519_key" ) @@ -94,23 +100,21 @@ const ( // // The escaping for SQL injections is handled in the SetPostgreSQLPassword // function + // #nosec: G101 sqlSetPasswordDefault = `ALTER ROLE %s PASSWORD %s;` ) -var ( - // ErrMissingConfigAnnotation represents an error thrown when the 'config' annotation is found - // to be missing from the 'config' configMap created to store cluster-wide configuration - ErrMissingConfigAnnotation error = errors.New("'config' annotation missing from cluster " + - "configutation") -) +// ErrMissingConfigAnnotation represents an error thrown when the 'config' annotation is found +// to be missing from the 'config' configMap created to store cluster-wide configuration +var ErrMissingConfigAnnotation error = errors.New("'config' annotation missing from cluster " + + "configutation") -var ( - // CmdStopPostgreSQL is the command used to stop a PostgreSQL instance, which - // uses the "fast" shutdown mode. This needs a data directory appended to it - cmdStopPostgreSQL = []string{"pg_ctl", "stop", - "-m", "fast", "-D", - } -) +// CmdStopPostgreSQL is the command used to stop a PostgreSQL instance, which +// uses the "fast" shutdown mode. This needs a data directory appended to it +var cmdStopPostgreSQL = []string{ + "pg_ctl", "stop", + "-m", "fast", "-D", +} // CreateBackrestRepoSecrets creates the secrets required to manage the // pgBackRest repo container @@ -229,7 +233,6 @@ func CreateBackrestRepoSecrets(clientset kubernetes.Interface, // IsAutofailEnabled - returns true if autofail label is set to true, false if not. func IsAutofailEnabled(cluster *crv1.Pgcluster) bool { - labels := cluster.ObjectMeta.Labels failLabel := labels[config.LABEL_AUTOFAIL] @@ -248,7 +251,6 @@ func GeneratedPasswordValidUntilDays(configuredValidUntilDays string) int { // note that "configuredPasswordLength" may be an empty string, and as such // the below line could fail. That's ok though! as we have a default set up validUntilDays, err := strconv.Atoi(configuredValidUntilDays) - // if there is an error...set it to a default if err != nil { validUntilDays = DefaultPasswordValidUntilDays @@ -269,7 +271,6 @@ func GetPrimaryPod(clientset kubernetes.Interface, cluster *crv1.Pgcluster) (*v1 // query the pods pods, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{LabelSelector: selector}) - // if there is an error, log it and abort if err != nil { return nil, err @@ -277,8 +278,7 @@ func GetPrimaryPod(clientset kubernetes.Interface, cluster *crv1.Pgcluster) (*v1 // if no pods are retirn, then also raise an error if len(pods.Items) == 0 { - err := errors.New(fmt.Sprintf("primary pod not found for selector [%s]", selector)) - return nil, err + return nil, fmt.Errorf("primary pod not found for selector %q", selector) } // Grab the first pod from the list as this is presumably the primary pod @@ -294,7 +294,6 @@ func GetS3CredsFromBackrestRepoSecret(clientset kubernetes.Interface, namespace, s3Secret := AWSS3Secret{} secret, err := clientset.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{}) - if err != nil { log.Error(err) return s3Secret, err diff --git a/internal/util/failover.go b/internal/util/failover.go index a4abba76f6..a19d887b25 100644 --- a/internal/util/failover.go +++ b/internal/util/failover.go @@ -91,12 +91,10 @@ const ( instanceStatusUnavailable = "unavailable" ) -var ( - // instanceInfoCommand is the command used to get information about the status - // and other statistics about the instances in a PostgreSQL cluster, e.g. - // replication lag - instanceInfoCommand = []string{"patronictl", "list", "-f", "json"} -) +// instanceInfoCommand is the command used to get information about the status +// and other statistics about the instances in a PostgreSQL cluster, e.g. +// replication lag +var instanceInfoCommand = []string{"patronictl", "list", "-f", "json"} // GetPod determines the best target to fail to func GetPod(clientset kubernetes.Interface, deploymentName, namespace string) (*v1.Pod, error) { @@ -115,13 +113,13 @@ func GetPod(clientset kubernetes.Interface, deploymentName, namespace string) (* return pod, errors.New("could not determine which pod to failover to") } - for _, v := range pods.Items { - pod = &v + for i := range pods.Items { + pod = &pods.Items[i] } found := false - //make sure the pod has a database container it it + // make sure the pod has a database container it it for _, c := range pod.Spec.Containers { if c.Name == "database" { found = true @@ -179,7 +177,6 @@ func ReplicationStatus(request ReplicationStatusRequest, includePrimary, include log.Debugf(`searching for pods with "%s"`, selector) pods, err := request.Clientset.CoreV1().Pods(request.Namespace).List(ctx, metav1.ListOptions{LabelSelector: selector}) - // If there is an error trying to get the pods, return here. Allow the caller // to handle the error if err != nil { @@ -204,9 +201,9 @@ func ReplicationStatus(request ReplicationStatusRequest, includePrimary, include // From executing and running a command in the first active pod var pod *v1.Pod - for _, p := range pods.Items { - if p.Status.Phase == v1.PodRunning { - pod = &p + for i := range pods.Items { + if pods.Items[i].Status.Phase == v1.PodRunning { + pod = &pods.Items[i] break } } @@ -236,7 +233,6 @@ func ReplicationStatus(request ReplicationStatusRequest, includePrimary, include commandStdOut, _, err := kubeapi.ExecToPodThroughAPI( request.RESTConfig, request.Clientset, instanceInfoCommand, pod.Spec.Containers[0].Name, pod.Name, request.Namespace, nil) - // if there is an error, return. We will log the error at a higher level if err != nil { return response, err @@ -244,7 +240,7 @@ func ReplicationStatus(request ReplicationStatusRequest, includePrimary, include // parse the JSON and plast it into instanceInfoList var rawInstances []instanceReplicationInfoJSON - json.Unmarshal([]byte(commandStdOut), &rawInstances) + _ = json.Unmarshal([]byte(commandStdOut), &rawInstances) log.Debugf("patroni instance info: %v", rawInstances) @@ -327,14 +323,14 @@ func ToggleAutoFailover(clientset kubernetes.Interface, enable bool, pghaScope, configJSONStr := configMap.ObjectMeta.Annotations["config"] var configJSON map[string]interface{} - json.Unmarshal([]byte(configJSONStr), &configJSON) + _ = json.Unmarshal([]byte(configJSONStr), &configJSON) if !enable { // disable autofail condition - disableFailover(clientset, configMap, configJSON, namespace) + _ = disableFailover(clientset, configMap, configJSON, namespace) } else { // enable autofail - enableFailover(clientset, configMap, configJSON, namespace) + _ = enableFailover(clientset, configMap, configJSON, namespace) } return nil @@ -344,7 +340,6 @@ func ToggleAutoFailover(clientset kubernetes.Interface, enable bool, pghaScope, // pods in a cluster to the a struct containing the associated instance name and the // Nodes that it runs on, all based upon the output from a Kubernetes API query func createInstanceInfoMap(pods *v1.PodList) map[string]instanceInfo { - instanceInfoMap := make(map[string]instanceInfo) // Iterate through each pod that is returned and get the mapping between the diff --git a/internal/util/pgbouncer.go b/internal/util/pgbouncer.go index 2fdd645126..0b3a9a528d 100644 --- a/internal/util/pgbouncer.go +++ b/internal/util/pgbouncer.go @@ -29,6 +29,7 @@ const pgBouncerConfigMapFormat = "%s-pgbouncer-cm" // pgBouncerSecretFormat is the name of the Kubernetes Secret that pgBouncer // uses that stores configuration and pgbouncer user information, and follows // the format "-pgbouncer-secret" +// #nosec: G101 const pgBouncerSecretFormat = "%s-pgbouncer-secret" // pgBouncerUserFileFormat is the format of what the pgBouncer user management diff --git a/internal/util/policy.go b/internal/util/policy.go index 2a0e2fcf83..6b19784dca 100644 --- a/internal/util/policy.go +++ b/internal/util/policy.go @@ -19,6 +19,7 @@ import ( "context" "errors" "fmt" + "io/ioutil" "net/http" "strings" @@ -26,8 +27,6 @@ import ( "github.com/crunchydata/postgres-operator/internal/kubeapi" pgo "github.com/crunchydata/postgres-operator/pkg/generated/clientset/versioned" - "io/ioutil" - log "github.com/sirupsen/logrus" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -38,9 +37,8 @@ import ( func ExecPolicy(clientset kubeapi.Interface, restconfig *rest.Config, namespace, policyName, serviceName, port string) error { ctx := context.TODO() - //fetch the policy sql + // fetch the policy sql sql, err := GetPolicySQL(clientset, namespace, policyName) - if err != nil { return err } @@ -136,7 +134,6 @@ func readSQLFromURL(urlstring string) (string, error) { } return string(bodyBytes), err - } // ValidatePolicy tests to see if a policy exists diff --git a/internal/util/secrets.go b/internal/util/secrets.go index eed3348d31..733392eabf 100644 --- a/internal/util/secrets.go +++ b/internal/util/secrets.go @@ -69,7 +69,6 @@ func CreateSecret(clientset kubernetes.Interface, db, secretName, username, pass _, err := clientset.CoreV1().Secrets(namespace).Create(ctx, &secret, metav1.CreateOptions{}) return err - } // GeneratePassword generates a password of a given length out of the acceptable @@ -79,7 +78,6 @@ func GeneratePassword(length int) (string, error) { for i := 0; i < length; i++ { char, err := rand.Int(rand.Reader, passwordCharSelector) - // if there is an error generating the random integer, return if err != nil { return "", err @@ -100,7 +98,6 @@ func GeneratedPasswordLength(configuredPasswordLength string) int { // note that "configuredPasswordLength" may be an empty string, and as such // the below line could fail. That's ok though! as we have a default set up generatedPasswordLength, err := strconv.Atoi(configuredPasswordLength) - // if there is an error...set it to a default if err != nil { generatedPasswordLength = DefaultGeneratedPasswordLength @@ -113,7 +110,6 @@ func GeneratedPasswordLength(configuredPasswordLength string) int { func GetPasswordFromSecret(clientset kubernetes.Interface, namespace, secretName string) (string, error) { ctx := context.TODO() secret, err := clientset.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{}) - if err != nil { return "", err } @@ -154,7 +150,6 @@ func UpdateUserSecret(clientset kubernetes.Interface, clustername, username, pas // see if the secret already exists secret, err := clientset.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{}) - // if this returns an error and it's not the "not found" error, return // However, if it is the "not found" error, treat this as creating the user // secret diff --git a/internal/util/ssh.go b/internal/util/ssh.go index aa886bbca7..e116716d12 100644 --- a/internal/util/ssh.go +++ b/internal/util/ssh.go @@ -91,6 +91,7 @@ func newPrivateKey(key ed25519.PrivateKey) ([]byte, error) { // check fields should match to easily verify // that a decryption was successful + // #nosec: G404 private.Check1 = rand.Uint32() private.Check2 = private.Check1 diff --git a/internal/util/util.go b/internal/util/util.go index 95a8310742..3559d09894 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -44,7 +44,6 @@ var gisImageTagRegex = regexp.MustCompile(`(.+-[\d|\.]+)-[\d|\.]+?(-[\d|\.]+.*)` func init() { rand.Seed(time.Now().UnixNano()) - } // GetLabels ... @@ -58,19 +57,19 @@ func GetLabels(name, clustername string, replica bool) string { return output } -//CurrentPrimaryUpdate prepares the needed data structures with the correct current primary value -//before passing them along to be patched into the current pgcluster CRD's annotations +// CurrentPrimaryUpdate prepares the needed data structures with the correct current primary value +// before passing them along to be patched into the current pgcluster CRD's annotations func CurrentPrimaryUpdate(clientset pgo.Interface, cluster *crv1.Pgcluster, currentPrimary, namespace string) error { - //create a new map + // create a new map metaLabels := make(map[string]string) - //copy the relevant values into the new map + // copy the relevant values into the new map for k, v := range cluster.ObjectMeta.Labels { metaLabels[k] = v } - //update this map with the new deployment label + // update this map with the new deployment label metaLabels[config.LABEL_DEPLOYMENT_NAME] = currentPrimary - //Update CRD with the current primary name and the new deployment to point to after the failover + // Update CRD with the current primary name and the new deployment to point to after the failover if err := PatchClusterCRD(clientset, metaLabels, cluster, currentPrimary, namespace); err != nil { log.Errorf("failoverlogic: could not patch pgcluster %s with the current primary", currentPrimary) } @@ -112,7 +111,6 @@ func PatchClusterCRD(clientset pgo.Interface, labelMap map[string]string, oldCrd Patch(ctx, oldCrd.Spec.Name, types.MergePatchType, patchBytes, metav1.PatchOptions{}) return err6 - } // GetValueOrDefault checks whether the first value given is set. If it is, @@ -149,7 +147,6 @@ func GetSecretPassword(clientset kubernetes.Interface, db, suffix, Namespace str log.Error("primary secret not found for " + db) return "", errors.New("primary secret not found for " + db) - } // GetStandardImageTag takes the current image name and the image tag value @@ -158,7 +155,6 @@ func GetSecretPassword(clientset kubernetes.Interface, db, suffix, Namespace str // the tag without the addition of the GIS version. This tag value can then // be used when provisioning containers using the standard containers tag. func GetStandardImageTag(imageName, imageTag string) string { - if imageName == "crunchy-postgres-gis-ha" && strings.Count(imageTag, "-") > 2 { return gisImageTagRegex.ReplaceAllString(imageTag, "$1$2") } @@ -170,6 +166,7 @@ func GetStandardImageTag(imageName, imageTag string) string { func RandStringBytesRmndr(n int) string { b := make([]byte, n) for i := range b { + // #nosec: G404 b[i] = letterBytes[rand.Int63()%int64(len(letterBytes))] } return string(b) diff --git a/internal/util/util_test.go b/internal/util/util_test.go index 30d6d8b65d..d867351a85 100644 --- a/internal/util/util_test.go +++ b/internal/util/util_test.go @@ -3,7 +3,6 @@ package util import "testing" func TestGetStandardImageTag(t *testing.T) { - assertCorrectMessage := func(t testing.TB, got, want string) { t.Helper() if got != want { diff --git a/pkg/apis/crunchydata.com/v1/common.go b/pkg/apis/crunchydata.com/v1/common.go index 242025f038..6bf14408dc 100644 --- a/pkg/apis/crunchydata.com/v1/common.go +++ b/pkg/apis/crunchydata.com/v1/common.go @@ -111,7 +111,6 @@ func (s PgStorageSpec) GetSupplementalGroups() []int64 { } supplementalGroup, err := strconv.Atoi(result) - // if there is an error, only warn about it and continue through the loop if err != nil { log.Warnf("malformed storage supplemental group: %v", err) diff --git a/pkg/apis/crunchydata.com/v1/register.go b/pkg/apis/crunchydata.com/v1/register.go index 00db119bda..7b7359f504 100644 --- a/pkg/apis/crunchydata.com/v1/register.go +++ b/pkg/apis/crunchydata.com/v1/register.go @@ -30,7 +30,7 @@ var ( ) // GroupName is the group name used in this package. -//const GroupName = "cr.client-go.k8s.io" +// const GroupName = "cr.client-go.k8s.io" const GroupName = "crunchydata.com" // SchemeGroupVersion is the group version used to register these objects. diff --git a/pkg/apis/crunchydata.com/v1/task.go b/pkg/apis/crunchydata.com/v1/task.go index 7b79896e67..463c532f80 100644 --- a/pkg/apis/crunchydata.com/v1/task.go +++ b/pkg/apis/crunchydata.com/v1/task.go @@ -23,7 +23,6 @@ import ( const PgtaskResourcePlural = "pgtasks" const ( - PgtaskDeleteBackups = "delete-backups" PgtaskDeleteData = "delete-data" PgtaskFailover = "failover" PgtaskAutoFailover = "autofailover" diff --git a/pkg/apiservermsgs/clustermsgs.go b/pkg/apiservermsgs/clustermsgs.go index 251e32c7cd..52b68909a2 100644 --- a/pkg/apiservermsgs/clustermsgs.go +++ b/pkg/apiservermsgs/clustermsgs.go @@ -82,7 +82,7 @@ type CreateClusterRequest struct { AutofailFlag bool ArchiveFlag bool BackrestStorageType string - //BackrestRestoreFrom string + // BackrestRestoreFrom string PgbouncerFlag bool // PgBouncerReplicas represents the total number of pgBouncer pods to deploy with a // PostgreSQL cluster. Only works if PgbouncerFlag is set, and if so, it must @@ -254,12 +254,14 @@ type ShowClusterService struct { BackrestRepo bool } -const PodTypePrimary = "primary" -const PodTypeReplica = "replica" -const PodTypePgbouncer = "pgbouncer" -const PodTypePgbackrest = "pgbackrest" -const PodTypeBackup = "backup" -const PodTypeUnknown = "unknown" +const ( + PodTypePrimary = "primary" + PodTypeReplica = "replica" + PodTypePgbouncer = "pgbouncer" + PodTypePgbackrest = "pgbackrest" + PodTypeBackup = "backup" + PodTypeUnknown = "unknown" +) // ShowClusterPod // diff --git a/pkg/apiservermsgs/usermsgs.go b/pkg/apiservermsgs/usermsgs.go index 1f0ba56295..9c63c3d483 100644 --- a/pkg/apiservermsgs/usermsgs.go +++ b/pkg/apiservermsgs/usermsgs.go @@ -31,11 +31,9 @@ const ( UpdateUserLoginDisable ) -var ( - // ErrPasswordTypeInvalid is used when a string that's not included in - // PasswordTypeStrings is used - ErrPasswordTypeInvalid = errors.New("invalid password type. choices are (md5, scram-sha-256)") -) +// ErrPasswordTypeInvalid is used when a string that's not included in +// PasswordTypeStrings is used +var ErrPasswordTypeInvalid = errors.New("invalid password type. choices are (md5, scram-sha-256)") // passwordTypeStrings is a mapping of strings of password types to their // corresponding value of the structured password type diff --git a/pkg/apiservermsgs/usermsgs_test.go b/pkg/apiservermsgs/usermsgs_test.go index d2f70388fc..207eda3757 100644 --- a/pkg/apiservermsgs/usermsgs_test.go +++ b/pkg/apiservermsgs/usermsgs_test.go @@ -16,6 +16,7 @@ package apiservermsgs */ import ( + "errors" "testing" pgpassword "github.com/crunchydata/postgres-operator/internal/postgres/password" @@ -33,7 +34,6 @@ func TestGetPasswordType(t *testing.T) { for passwordTypeStr, expected := range tests { t.Run(passwordTypeStr, func(t *testing.T) { passwordType, err := GetPasswordType(passwordTypeStr) - if err != nil { t.Error(err) return @@ -54,7 +54,7 @@ func TestGetPasswordType(t *testing.T) { for passwordTypeStr, expected := range tests { t.Run(passwordTypeStr, func(t *testing.T) { - if _, err := GetPasswordType(passwordTypeStr); err != expected { + if _, err := GetPasswordType(passwordTypeStr); !errors.Is(err, expected) { t.Errorf("password type %q should yield error %q", passwordTypeStr, expected.Error()) } }) diff --git a/pkg/events/eventing.go b/pkg/events/eventing.go index 96b544405b..5c40861352 100644 --- a/pkg/events/eventing.go +++ b/pkg/events/eventing.go @@ -19,17 +19,18 @@ import ( "encoding/json" "errors" "fmt" - crunchylog "github.com/crunchydata/postgres-operator/internal/logging" - "github.com/nsqio/go-nsq" - log "github.com/sirupsen/logrus" "os" "reflect" "time" + + crunchylog "github.com/crunchydata/postgres-operator/internal/logging" + "github.com/nsqio/go-nsq" + log "github.com/sirupsen/logrus" ) // String returns the string form for a given LogLevel func Publish(e EventInterface) error { - //Add logging configuration + // Add logging configuration crunchylog.CrunchyLogger(crunchylog.SetParameters()) eventAddr := os.Getenv("EVENT_ADDR") if eventAddr == "" { @@ -41,7 +42,7 @@ func Publish(e EventInterface) error { } cfg := nsq.NewConfig() - //cfg.UserAgent = fmt.Sprintf("to_nsq/%s go-nsq/%s", version.Binary, nsq.VERSION) + // cfg.UserAgent = fmt.Sprintf("to_nsq/%s go-nsq/%s", version.Binary, nsq.VERSION) cfg.UserAgent = fmt.Sprintf("go-nsq/%s", nsq.VERSION) log.Debugf("publishing %s message %s", reflect.TypeOf(e), e.String()) @@ -78,7 +79,7 @@ func Publish(e EventInterface) error { } } - //always publish to the All topic + // always publish to the All topic err = producer.Publish(EventTopicAll, b) if err != nil { log.Errorf("Error: %s", err) diff --git a/pkg/events/eventtype.go b/pkg/events/eventtype.go index 43a709abde..95a09f8121 100644 --- a/pkg/events/eventtype.go +++ b/pkg/events/eventtype.go @@ -33,6 +33,7 @@ const ( EventTopicPGOUser = "pgousertopic" EventTopicUpgrade = "upgradetopic" ) + const ( EventReloadCluster = "ReloadCluster" EventPrimaryNotReady = "PrimaryNotReady" @@ -161,6 +162,7 @@ type EventCreateClusterCompletedFormat struct { func (p EventCreateClusterCompletedFormat) GetHeader() EventHeader { return p.EventHeader } + func (lvl EventCreateClusterCompletedFormat) String() string { msg := fmt.Sprintf("Event %s - (create cluster completed) clustername %s workflow %s", lvl.EventHeader, lvl.Clustername, lvl.WorkflowID) return msg From 26e6144fe315b7ddf26dced2e0a8a447498ecff1 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 21 Dec 2020 16:13:27 -0500 Subject: [PATCH 062/276] Return an error with invalid type for backup options If we cannot determine the type for a backup option, return an error instead of just passing through. --- internal/apiserver/backupoptions/backupoptionsutil.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/apiserver/backupoptions/backupoptionsutil.go b/internal/apiserver/backupoptions/backupoptionsutil.go index 5cf5901b29..b31bd8e00f 100644 --- a/internal/apiserver/backupoptions/backupoptionsutil.go +++ b/internal/apiserver/backupoptions/backupoptionsutil.go @@ -89,7 +89,8 @@ func convertBackupOptsToStruct(backupOpts string, request interface{}) (backupOp commandLine.BoolVarP(fieldVal.Addr().Interface().(*bool), flag, flagShort, false, "") case reflect.Slice: commandLine.StringArrayVarP(fieldVal.Addr().Interface().(*[]string), flag, flagShort, nil, "") - default: // no-op + default: + return nil, nil, fmt.Errorf("invalid value for (%q/%q): %v", flag, flagShort, fieldVal) } } } From 590e315a18549f0ddc41953c02f1153c3ad278c2 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 21 Dec 2020 16:25:00 -0500 Subject: [PATCH 063/276] Update policy format acceptance Policies can now only be executed from SQL that is stored in a "pgpolicies.crunchydata.com" custom resource, or loaded in via the `pgo create policy -in-file`. --- cmd/pgo/cmd/create.go | 7 +++--- cmd/pgo/cmd/policy.go | 4 ---- .../pgo-client/reference/pgo_create_policy.md | 5 ++-- .../apiserver/policyservice/policyimpl.go | 3 +-- .../apiserver/policyservice/policyservice.go | 2 +- internal/util/policy.go | 24 +------------------ pkg/apis/crunchydata.com/v1/policy.go | 1 - pkg/apiservermsgs/policymsgs.go | 1 - 8 files changed, 8 insertions(+), 39 deletions(-) diff --git a/cmd/pgo/cmd/create.go b/cmd/pgo/cmd/create.go index eaa60e43fa..2e6bf8c41e 100644 --- a/cmd/pgo/cmd/create.go +++ b/cmd/pgo/cmd/create.go @@ -38,7 +38,7 @@ var ( Database string Password string SecretFrom string - PoliciesFlag, PolicyFile, PolicyURL string + PoliciesFlag, PolicyFile string UserLabels string Tablespaces []string ServiceType string @@ -224,8 +224,8 @@ var createPolicyCmd = &cobra.Command{ Namespace = PGONamespace } log.Debug("create policy called ") - if PolicyFile == "" && PolicyURL == "" { - fmt.Println(`Error: The --in-file or --url flags are required to create a policy.`) + if PolicyFile == "" { + fmt.Println(`Error: The --in-file is required to create a policy.`) return } @@ -521,7 +521,6 @@ func init() { // "pgo create policy" flags createPolicyCmd.Flags().StringVarP(&PolicyFile, "in-file", "i", "", "The policy file path to use for adding a policy.") - createPolicyCmd.Flags().StringVarP(&PolicyURL, "url", "u", "", "The url to use for adding a policy.") // "pgo create schedule" flags createScheduleCmd.Flags().StringVarP(&ScheduleDatabase, "database", "", "", "The database to run the SQL policy against.") diff --git a/cmd/pgo/cmd/policy.go b/cmd/pgo/cmd/policy.go index 96a858efc9..ed4aae82a8 100644 --- a/cmd/pgo/cmd/policy.go +++ b/cmd/pgo/cmd/policy.go @@ -139,7 +139,6 @@ func showPolicy(args []string, ns string) { for _, policy := range response.PolicyList.Items { fmt.Println("") fmt.Println("policy : " + policy.Spec.Name) - fmt.Println(TreeBranch + "url : " + policy.Spec.URL) fmt.Println(TreeBranch + "status : " + policy.Spec.Status) fmt.Println(TreeTrunk + "sql : " + policy.Spec.SQL) } @@ -158,9 +157,6 @@ func createPolicy(args []string, ns string) { r.Namespace = ns r.ClientVersion = msgs.PGO_VERSION - if PolicyURL != "" { - r.URL = PolicyURL - } if PolicyFile != "" { r.SQL, err = getPolicyString(PolicyFile) diff --git a/docs/content/pgo-client/reference/pgo_create_policy.md b/docs/content/pgo-client/reference/pgo_create_policy.md index ac17b059f6..7705067853 100644 --- a/docs/content/pgo-client/reference/pgo_create_policy.md +++ b/docs/content/pgo-client/reference/pgo_create_policy.md @@ -20,13 +20,12 @@ pgo create policy [flags] ``` -h, --help help for policy -i, --in-file string The policy file path to use for adding a policy. - -u, --url string The url to use for adding a policy. ``` ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -40,4 +39,4 @@ pgo create policy [flags] * [pgo create](/pgo-client/reference/pgo_create/) - Create a Postgres Operator resource -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 21-Dec-2020 diff --git a/internal/apiserver/policyservice/policyimpl.go b/internal/apiserver/policyservice/policyimpl.go index a3153536bc..21b77d4d63 100644 --- a/internal/apiserver/policyservice/policyimpl.go +++ b/internal/apiserver/policyservice/policyimpl.go @@ -37,7 +37,7 @@ import ( ) // CreatePolicy ... -func CreatePolicy(client pgo.Interface, policyName, policyURL, policyFile, ns, pgouser string) (bool, error) { +func CreatePolicy(client pgo.Interface, policyName, policyFile, ns, pgouser string) (bool, error) { ctx := context.TODO() log.Debugf("create policy called for %s", policyName) @@ -46,7 +46,6 @@ func CreatePolicy(client pgo.Interface, policyName, policyURL, policyFile, ns, p spec := crv1.PgpolicySpec{} spec.Namespace = ns spec.Name = policyName - spec.URL = policyURL spec.SQL = policyFile myLabels := make(map[string]string) diff --git a/internal/apiserver/policyservice/policyservice.go b/internal/apiserver/policyservice/policyservice.go index 4324faac21..00a499be64 100644 --- a/internal/apiserver/policyservice/policyservice.go +++ b/internal/apiserver/policyservice/policyservice.go @@ -83,7 +83,7 @@ func CreatePolicyHandler(w http.ResponseWriter, r *http.Request) { resp.Status.Msg = "invalid policy name format " + errs[0] } else { - found, err := CreatePolicy(apiserver.Clientset, request.Name, request.URL, request.SQL, ns, username) + found, err := CreatePolicy(apiserver.Clientset, request.Name, request.SQL, ns, username) if err != nil { log.Error(err.Error()) resp.Status.Code = msgs.Error diff --git a/internal/util/policy.go b/internal/util/policy.go index 6b19784dca..25fe2953d7 100644 --- a/internal/util/policy.go +++ b/internal/util/policy.go @@ -19,8 +19,6 @@ import ( "context" "errors" "fmt" - "io/ioutil" - "net/http" "strings" "github.com/crunchydata/postgres-operator/internal/config" @@ -106,10 +104,7 @@ func GetPolicySQL(clientset pgo.Interface, namespace, policyName string) (string ctx := context.TODO() p, err := clientset.CrunchydataV1().Pgpolicies(namespace).Get(ctx, policyName, metav1.GetOptions{}) if err == nil { - if p.Spec.URL != "" { - return readSQLFromURL(p.Spec.URL) - } - return p.Spec.SQL, err + return p.Spec.SQL, nil } if kerrors.IsNotFound(err) { @@ -119,23 +114,6 @@ func GetPolicySQL(clientset pgo.Interface, namespace, policyName string) (string return "", err } -// readSQLFromURL returns the SQL string from a URL -func readSQLFromURL(urlstring string) (string, error) { - var bodyBytes []byte - response, err := http.Get(urlstring) - if err == nil { - bodyBytes, err = ioutil.ReadAll(response.Body) - defer response.Body.Close() - } - - if err != nil { - log.Error(err) - return "", err - } - - return string(bodyBytes), err -} - // ValidatePolicy tests to see if a policy exists func ValidatePolicy(clientset pgo.Interface, namespace string, policyName string) error { ctx := context.TODO() diff --git a/pkg/apis/crunchydata.com/v1/policy.go b/pkg/apis/crunchydata.com/v1/policy.go index 28347f9950..904567d496 100644 --- a/pkg/apis/crunchydata.com/v1/policy.go +++ b/pkg/apis/crunchydata.com/v1/policy.go @@ -27,7 +27,6 @@ const PgpolicyResourcePlural = "pgpolicies" type PgpolicySpec struct { Namespace string `json:"namespace"` Name string `json:"name"` - URL string `json:"url"` SQL string `json:"sql"` Status string `json:"status"` } diff --git a/pkg/apiservermsgs/policymsgs.go b/pkg/apiservermsgs/policymsgs.go index ec3e7cf2f9..ba5c6cea21 100644 --- a/pkg/apiservermsgs/policymsgs.go +++ b/pkg/apiservermsgs/policymsgs.go @@ -33,7 +33,6 @@ type ShowPolicyRequest struct { // swagger:model type CreatePolicyRequest struct { Name string - URL string SQL string Namespace string ClientVersion string From 449b9d4d9c6824b5c5598447a95319a5e871257c Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 21 Dec 2020 16:04:23 -0500 Subject: [PATCH 064/276] Update linter CI to include "default" as exhaustive There are some switch statements that may have a larger amount of values that can be considered, and as such we should allow for default to mean all of them. This now also runs on the full code base each time a pull request is opened. Additionally, it disables the gofumpt check. --- .github/workflows/lint.yaml | 1 - .golangci.yaml | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 29e173c4fa..40ee71c8e9 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -12,4 +12,3 @@ jobs: with: version: v1.32 args: --timeout=5m - only-new-issues: true diff --git a/.golangci.yaml b/.golangci.yaml index c8ac7c76ed..9918bb1a8b 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -2,6 +2,7 @@ linters: disable: + - gofumpt - scopelint enable: - gosimple @@ -11,6 +12,10 @@ linters: - format - unused +linters-settings: + exhaustive: + default-signifies-exhaustive: true + run: skip-dirs: - pkg/generated From eaa88138bc866ab58b4ebb12f0782b38ad3b75e5 Mon Sep 17 00:00:00 2001 From: Joseph Mckulka <16840147+jmckulk@users.noreply.github.com> Date: Tue, 22 Dec 2020 15:30:22 -0500 Subject: [PATCH 065/276] Ensure PgBackRest env vars use index The PostgreSQL Operator currently utilizes two different formats for defining pgBackRest "repo" settings via environment variables. For instance, certain variables are defined with an index (e.g. `PGBACKREST_REPO1_`), while others are not(e.g. `PGBACKREST_REPO_`). This inconsistency has led to confusion when using the operator. The operator will now standardize on using an indexed variable. Issue: [ch9871] --- .../pgo-operator/files/pgo-configs/backrest-job.json | 8 ++++---- .../files/pgo-configs/pgo-backrest-repo-template.json | 4 ++-- internal/operator/backrest/backup.go | 8 ++++---- internal/operator/backrest/repo.go | 10 +++++----- internal/operator/clusterutilities.go | 4 ++-- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json index dddc0b14d9..bf89971aa6 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json @@ -57,11 +57,11 @@ "name": "PGBACKREST_DB_PATH", "value": "{{.PgbackrestDBPath}}" }, { - "name": "PGBACKREST_REPO_PATH", - "value": "{{.PgbackrestRepoPath}}" + "name": "PGBACKREST_REPO1_PATH", + "value": "{{.PgbackrestRepo1Path}}" }, { - "name": "PGBACKREST_REPO_TYPE", - "value": "{{.PgbackrestRepoType}}" + "name": "PGBACKREST_REPO1_TYPE", + "value": "{{.PgbackrestRepo1Type}}" },{ "name": "PGHA_PGBACKREST_LOCAL_S3_STORAGE", "value": "{{.BackrestLocalAndS3Storage}}" diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json index 30b69dc4b6..885396322b 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json @@ -69,8 +69,8 @@ "value": "{{.PgbackrestDBPath}}" }, { - "name": "PGBACKREST_REPO_PATH", - "value": "{{.PgbackrestRepoPath}}" + "name": "PGBACKREST_REPO1_PATH", + "value": "{{.PgbackrestRepo1Path}}" }, { "name": "PGBACKREST_PG1_PORT", diff --git a/internal/operator/backrest/backup.go b/internal/operator/backrest/backup.go index 41a03a3d89..e486104961 100644 --- a/internal/operator/backrest/backup.go +++ b/internal/operator/backrest/backup.go @@ -55,8 +55,8 @@ type backrestJobTemplateFields struct { SecurityContext string PgbackrestStanza string PgbackrestDBPath string - PgbackrestRepoPath string - PgbackrestRepoType string + PgbackrestRepo1Path string + PgbackrestRepo1Type string BackrestLocalAndS3Storage bool PgbackrestS3VerifyTLS string PgbackrestRestoreVolumes string @@ -88,10 +88,10 @@ func Backrest(namespace string, clientset kubernetes.Interface, task *crv1.Pgtas CCPImageTag: operator.Pgo.Cluster.CCPImageTag, PgbackrestStanza: task.Spec.Parameters[config.LABEL_PGBACKREST_STANZA], PgbackrestDBPath: task.Spec.Parameters[config.LABEL_PGBACKREST_DB_PATH], - PgbackrestRepoPath: task.Spec.Parameters[config.LABEL_PGBACKREST_REPO_PATH], + PgbackrestRepo1Path: task.Spec.Parameters[config.LABEL_PGBACKREST_REPO_PATH], PgbackrestRestoreVolumes: "", PgbackrestRestoreVolumeMounts: "", - PgbackrestRepoType: operator.GetRepoType(task.Spec.Parameters[config.LABEL_BACKREST_STORAGE_TYPE]), + PgbackrestRepo1Type: operator.GetRepoType(task.Spec.Parameters[config.LABEL_BACKREST_STORAGE_TYPE]), BackrestLocalAndS3Storage: operator.IsLocalAndS3Storage(task.Spec.Parameters[config.LABEL_BACKREST_STORAGE_TYPE]), PgbackrestS3VerifyTLS: task.Spec.Parameters[config.LABEL_BACKREST_S3_VERIFY_TLS], } diff --git a/internal/operator/backrest/repo.go b/internal/operator/backrest/repo.go index 988b279548..f36fee6dce 100644 --- a/internal/operator/backrest/repo.go +++ b/internal/operator/backrest/repo.go @@ -50,12 +50,12 @@ type RepoDeploymentTemplateFields struct { BackrestRepoClaimName string SshdSecretsName string PGbackrestDBHost string - PgbackrestRepoPath string + PgbackrestRepo1Path string PgbackrestDBPath string PgbackrestPGPort string SshdPort int PgbackrestStanza string - PgbackrestRepoType string + PgbackrestRepo1Type string PgbackrestS3EnvVars string Name string ClusterName string @@ -197,7 +197,7 @@ func setBootstrapRepoOverrides(clientset kubernetes.Interface, cluster *crv1.Pgc return err } - repoFields.PgbackrestRepoPath = restoreFromSecret.Annotations[config.ANNOTATION_REPO_PATH] + repoFields.PgbackrestRepo1Path = restoreFromSecret.Annotations[config.ANNOTATION_REPO_PATH] repoFields.PgbackrestPGPort = restoreFromSecret.Annotations[config.ANNOTATION_PG_PORT] sshdPort, err := strconv.Atoi(restoreFromSecret.Annotations[config.ANNOTATION_SSHD_PORT]) @@ -234,12 +234,12 @@ func getRepoDeploymentFields(clientset kubernetes.Interface, cluster *crv1.Pgclu BackrestRepoClaimName: fmt.Sprintf(util.BackrestRepoPVCName, cluster.Name), SshdSecretsName: fmt.Sprintf(util.BackrestRepoSecretName, cluster.Name), PGbackrestDBHost: cluster.Name, - PgbackrestRepoPath: util.GetPGBackRestRepoPath(*cluster), + PgbackrestRepo1Path: util.GetPGBackRestRepoPath(*cluster), PgbackrestDBPath: "/pgdata/" + cluster.Name, PgbackrestPGPort: cluster.Spec.Port, SshdPort: operator.Pgo.Cluster.BackrestPort, PgbackrestStanza: "db", - PgbackrestRepoType: operator.GetRepoType(cluster.Spec.UserLabels[config.LABEL_BACKREST_STORAGE_TYPE]), + PgbackrestRepo1Type: operator.GetRepoType(cluster.Spec.UserLabels[config.LABEL_BACKREST_STORAGE_TYPE]), PgbackrestS3EnvVars: operator.GetPgbackrestS3EnvVars(*cluster, clientset, namespace), Name: fmt.Sprintf(util.BackrestRepoServiceName, cluster.Name), ClusterName: cluster.Name, diff --git a/internal/operator/clusterutilities.go b/internal/operator/clusterutilities.go index da27c3c614..776c264579 100644 --- a/internal/operator/clusterutilities.go +++ b/internal/operator/clusterutilities.go @@ -54,9 +54,9 @@ const ( PGHAConfigInitSetting = "init" // PGHAConfigReplicaBootstrapRepoType defines an override for the type of repo (local, S3, etc.) // that should be utilized when bootstrapping a replica (i.e. it override the - // PGBACKREST_REPO_TYPE env var in the environment). Allows for dynamic changing of the + // PGBACKREST_REPO1_TYPE env var in the environment). Allows for dynamic changing of the // backrest repo type without requiring container restarts (as would be required to update - // PGBACKREST_REPO_TYPE). + // PGBACKREST_REPO1_TYPE). PGHAConfigReplicaBootstrapRepoType = "replica-bootstrap-repo-type" ) From 96cc73248f9c8f9cbb10cb96b384357a253b1585 Mon Sep 17 00:00:00 2001 From: Chris Bandy Date: Thu, 24 Sep 2020 11:41:28 -0500 Subject: [PATCH 066/276] Build the GCP Marketplace installer with a smaller context This reduces the time it takes to build with Docker. --- installers/gcp-marketplace/Dockerfile | 18 +++++++++--------- installers/gcp-marketplace/Makefile | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/installers/gcp-marketplace/Dockerfile b/installers/gcp-marketplace/Dockerfile index adf85a355a..0e84cb2f27 100644 --- a/installers/gcp-marketplace/Dockerfile +++ b/installers/gcp-marketplace/Dockerfile @@ -20,21 +20,21 @@ RUN apt-get update \ && apt-get install -y --no-install-recommends ansible=2.9.* openssh-client \ && rm -rf /var/lib/apt/lists/* -COPY installers/ansible/* \ +COPY ansible/* \ /opt/postgres-operator/ansible/ -COPY installers/favicon.png \ - installers/gcp-marketplace/install-job.yaml \ - installers/gcp-marketplace/install.sh \ - installers/gcp-marketplace/values.yaml \ +COPY favicon.png \ + gcp-marketplace/install-job.yaml \ + gcp-marketplace/install.sh \ + gcp-marketplace/values.yaml \ /opt/postgres-operator/ -COPY installers/gcp-marketplace/install-hook.sh \ +COPY gcp-marketplace/install-hook.sh \ /bin/create_manifests.sh -COPY installers/gcp-marketplace/schema.yaml \ +COPY gcp-marketplace/schema.yaml \ /data/ -COPY installers/gcp-marketplace/application.yaml \ +COPY gcp-marketplace/application.yaml \ /data/manifest/ -COPY installers/gcp-marketplace/test-pod.yaml \ +COPY gcp-marketplace/test-pod.yaml \ /data-test/manifest/ ARG PGO_VERSION diff --git a/installers/gcp-marketplace/Makefile b/installers/gcp-marketplace/Makefile index f10f5b7c27..6236ae3ad8 100644 --- a/installers/gcp-marketplace/Makefile +++ b/installers/gcp-marketplace/Makefile @@ -37,12 +37,12 @@ image: image-$(IMAGE_BUILDER) .PHONY: image-buildah image-buildah: ## Build the deployer image with Buildah - sudo buildah bud --file Dockerfile --tag '$(DEPLOYER_IMAGE)' $(IMAGE_BUILD_ARGS) --layers ../.. + sudo buildah bud --file Dockerfile --tag '$(DEPLOYER_IMAGE)' $(IMAGE_BUILD_ARGS) --layers .. sudo buildah push '$(DEPLOYER_IMAGE)' docker-daemon:'$(DEPLOYER_IMAGE)' .PHONY: image-docker image-docker: ## Build the deployer image with Docker - docker build --file Dockerfile --tag '$(DEPLOYER_IMAGE)' $(IMAGE_BUILD_ARGS) ../.. + docker build --file Dockerfile --tag '$(DEPLOYER_IMAGE)' $(IMAGE_BUILD_ARGS) .. # PARAMETERS='{"OPERATOR_NAMESPACE": "", "OPERATOR_NAME": "", "OPERATOR_ADMIN_PASSWORD": ""}' .PHONY: install From a8db592c10b201c2b228d1f87149a80bed776018 Mon Sep 17 00:00:00 2001 From: Chris Bandy Date: Tue, 22 Dec 2020 12:54:57 -0600 Subject: [PATCH 067/276] Wait for RBAC reconciliation in GCP Marketplace Marketplace verification checks that the namespace has no RBAC objects after the application is removed. These objects used to be created during the install, but now they are created by the operator during reconcile. See: 78b39759cef7f6994165a937348291f03500db5c Issue: [ch10000] --- installers/gcp-marketplace/install.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/installers/gcp-marketplace/install.sh b/installers/gcp-marketplace/install.sh index 6dc770b993..3b65f8976d 100755 --- a/installers/gcp-marketplace/install.sh +++ b/installers/gcp-marketplace/install.sh @@ -37,16 +37,36 @@ resources=( clusterrolebinding/pgo-cluster-role configmap/pgo-config deployment/postgres-operator + role/pgo-backrest-role + role/pgo-pg-role role/pgo-role + role/pgo-target-role + rolebinding/pgo-backrest-role-binding + rolebinding/pgo-pg-role-binding rolebinding/pgo-role + rolebinding/pgo-target-role-binding secret/pgo.tls secret/pgo-backrest-repo-config secret/pgorole-pgoadmin secret/pgouser-admin service/postgres-operator + serviceaccount/pgo-backrest + serviceaccount/pgo-default + serviceaccount/pgo-pg + serviceaccount/pgo-target serviceaccount/postgres-operator ) for resource in "${resources[@]}"; do + kind="${resource%/*}" + name="${resource#*/}" + + for _ in $(seq 5); do + if [ "$( kc get "$kind" --field-selector="metadata.name=$name" --output=name )" ] + then break + else sleep 1s + fi + done + kc patch "$resource" --type=strategic --patch="$application_ownership" done From b680e59663e4c10a99ec9213a29c2d2c8b0451dc Mon Sep 17 00:00:00 2001 From: Chris Bandy Date: Tue, 22 Dec 2020 12:46:48 -0600 Subject: [PATCH 068/276] No ownership on default pgBackRest configuration This object is no longer created during install. See: 29ef4855cba68ddcc4dee13a21b697315e5fc88e --- installers/gcp-marketplace/install.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/installers/gcp-marketplace/install.sh b/installers/gcp-marketplace/install.sh index 3b65f8976d..cbe6d6890d 100755 --- a/installers/gcp-marketplace/install.sh +++ b/installers/gcp-marketplace/install.sh @@ -46,7 +46,6 @@ resources=( rolebinding/pgo-role rolebinding/pgo-target-role-binding secret/pgo.tls - secret/pgo-backrest-repo-config secret/pgorole-pgoadmin secret/pgouser-admin service/postgres-operator From 44358d4fd07218becb298d86a126a645917f8ac2 Mon Sep 17 00:00:00 2001 From: Chris Bandy Date: Tue, 22 Dec 2020 13:46:19 -0600 Subject: [PATCH 069/276] Verify GCP Marketplace installer without parameters To be on the marketplace, the deployer image must pass verification without any specified parameters. Contrary to the documentation, the verify command still takes parameters which allows us to test different configuration values. See: https://github.com/GoogleCloudPlatform/marketplace-k8s-app-tools/commit/aca27e694dc2 Issue: [ch10000] --- installers/gcp-marketplace/Dockerfile | 2 ++ installers/gcp-marketplace/test-schema.yaml | 6 ++++++ 2 files changed, 8 insertions(+) create mode 100644 installers/gcp-marketplace/test-schema.yaml diff --git a/installers/gcp-marketplace/Dockerfile b/installers/gcp-marketplace/Dockerfile index 0e84cb2f27..464e7d74fd 100644 --- a/installers/gcp-marketplace/Dockerfile +++ b/installers/gcp-marketplace/Dockerfile @@ -36,6 +36,8 @@ COPY gcp-marketplace/application.yaml \ /data/manifest/ COPY gcp-marketplace/test-pod.yaml \ /data-test/manifest/ +COPY gcp-marketplace/test-schema.yaml \ + /data-test/schema.yaml ARG PGO_VERSION RUN for file in \ diff --git a/installers/gcp-marketplace/test-schema.yaml b/installers/gcp-marketplace/test-schema.yaml new file mode 100644 index 0000000000..5dae182d7e --- /dev/null +++ b/installers/gcp-marketplace/test-schema.yaml @@ -0,0 +1,6 @@ +properties: + OPERATOR_ADMIN_PASSWORD: + type: string + default: insecure + x-google-marketplace: + type: MASKED_FIELD From 00f4c72bca641f22f9279f6a1d66442e4eff3f71 Mon Sep 17 00:00:00 2001 From: Chris Bandy Date: Tue, 22 Dec 2020 14:22:07 -0600 Subject: [PATCH 070/276] Move GCP Marketplace service account description Recent versions of marketplace verification expect a service account's description under a different schema key. Tested with tools v0.10.10. See: https://github.com/GoogleCloudPlatform/marketplace-k8s-app-tools/commit/10c92d4bb52c Issue: [ch10000] --- installers/gcp-marketplace/schema.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/installers/gcp-marketplace/schema.yaml b/installers/gcp-marketplace/schema.yaml index 6f0ec5320f..6b7e3df965 100644 --- a/installers/gcp-marketplace/schema.yaml +++ b/installers/gcp-marketplace/schema.yaml @@ -11,13 +11,13 @@ properties: INSTALLER_SERVICE_ACCOUNT: # This key appears in the ClusterRoleBinding name. title: Cluster Admin Service Account - description: >- - Name of a service account in the target namespace that has cluster-admin permissions. - This is used by the operator installer to create Custom Resource Definitions. type: string x-google-marketplace: type: SERVICE_ACCOUNT serviceAccount: + description: >- + Name of a service account in the target namespace that has cluster-admin permissions. + This is used by the operator installer to create Custom Resource Definitions. roles: - type: ClusterRole rulesType: PREDEFINED From 67d9b38df20233b76e7b8500c786e464fd45cc52 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Thu, 24 Dec 2020 09:54:20 -0500 Subject: [PATCH 071/276] Revise example custom resources around monitoring This ensures that the "exporter" attribute is present in the example, though it is defaulted to false. --- docs/content/custom-resources/_index.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/content/custom-resources/_index.md b/docs/content/custom-resources/_index.md index b39dd3ae0b..6a578bc242 100644 --- a/docs/content/custom-resources/_index.md +++ b/docs/content/custom-resources/_index.md @@ -312,7 +312,6 @@ metadata: backrest-storage-type: "s3" crunchy-pgbadger: "false" crunchy-pgha-scope: ${pgo_cluster_name} - crunchy-postgres-exporter: "false" deployment-name: ${pgo_cluster_name} name: ${pgo_cluster_name} pg-cluster: ${pgo_cluster_name} @@ -363,6 +362,7 @@ spec: clustername: ${pgo_cluster_name} customconfig: "" database: ${pgo_cluster_name} + exporter: false exporterport: "9187" limits: {} name: ${pgo_cluster_name} @@ -506,7 +506,6 @@ To enable the [monitoring]({{< relref "/architecture/monitoring.md">}}) (aka metrics) sidecar using the `crunchy-postgres-exporter` container, you need to set the `exporter` attribute in `pgclusters.crunchydata.com` custom resource. - ### Add a Tablespace Tablespaces can be added during the lifetime of a PostgreSQL cluster (tablespaces can be removed as well, but for a detailed explanation as to how, please see the [Tablespaces]({{< relref "/architecture/tablespaces.md">}}) section). From c9ea08397725868b050259589fee295de94e7d03 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Fri, 25 Dec 2020 11:21:06 -0500 Subject: [PATCH 072/276] Documentation around HA troubleshooting scenarios This provides an analysis around a scenario that can crop up with a failure around synchronous replication and how to troubleshoot and exit the situation. Issue: #2132 --- docs/content/tutorial/high-availability.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/content/tutorial/high-availability.md b/docs/content/tutorial/high-availability.md index a3c2a12bea..3a528ab456 100644 --- a/docs/content/tutorial/high-availability.md +++ b/docs/content/tutorial/high-availability.md @@ -96,6 +96,20 @@ Please understand the tradeoffs of synchronous replication before using it. To leran how to use pod anti-affinity and node affinity, please refer to the [high availability architecture documentation]({{< relref "architecture/high-availability/_index.md" >}}) +## Troubleshooting + +### No Primary Available After Both Synchronous Replication Instances Fail + +Though synchronous replication is available for guarding against transaction loss for [write sensitive workloads]({{< relref "architecture/high-availability/_index.md" >}}#synchronous-replication-guarding-against-transactions-loss), by default the high availability systems prefers availability over consistency and will continue to accept writes to a primary even if a replica fails. Additionally, in most scenarios, a system using synchronous replication will be able to recover and self heal should a primary or a replica go down. + +However, in the case that both a primary and its synchronous replica go down at the same time, a new primary may not be promoted. To guard against transaction loss, the high availability system will not promote any instances if it cannot determine if they had been one of the synchronous instances. As such, when it recovers, it will bring up all the instances as replicas. + +To get out of this situation, inspect the replicas using `pgo failover --query` to determine the best candidate (typically the one with the least amount of replication lag). After determining the best candidate, promote one of the replicas using `pgo failover --target` command. + +If you are still having issues, you may need to execute into one of the Pods and inspect the state with the `patronictl` command. + +A detailed breakdown of this case be found [here](https://github.com/CrunchyData/postgres-operator/issues/2132#issuecomment-748719843). + ## Next Steps Backups, restores, point-in-time-recoveries: [disaster recovery]({{< relref "architecture/disaster-recovery.md" >}}) is a big topic! We'll learn about you can [perform disaster recovery]({{< relref "tutorial/disaster-recovery.md" >}}) and more in the PostgreSQL Operator. From b471e7160cfa6cfed1decac32ec57656fb47e05c Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sat, 26 Dec 2020 15:14:47 -0500 Subject: [PATCH 073/276] Modify character space for random password generation This removes a couple of characters from consideration for the randomly generated passwords, as these characters could pose problems when applying them in shell environments. The character entropy is still quite large even with this removal. --- internal/util/secrets.go | 19 ++++++++++++++++--- internal/util/secrets_test.go | 12 +++++++++--- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/internal/util/secrets.go b/internal/util/secrets.go index 733392eabf..c8509e332f 100644 --- a/internal/util/secrets.go +++ b/internal/util/secrets.go @@ -46,6 +46,10 @@ const ( // passwordCharUpper is the highest ASCII character to use for generating a // password, which is 126 passwordCharUpper = 126 + // passwordCharExclude is a map of characters that we choose to exclude from + // the password to simplify usage in the shell. There is still enough entropy + // that exclusion of these characters is OK. + passwordCharExclude = "`\\" ) // passwordCharSelector is a "big int" that we need to select the random ASCII @@ -75,15 +79,24 @@ func CreateSecret(clientset kubernetes.Interface, db, secretName, username, pass // ASCII characters suitable for a password func GeneratePassword(length int) (string, error) { password := make([]byte, length) + i := 0 - for i := 0; i < length; i++ { - char, err := rand.Int(rand.Reader, passwordCharSelector) + for i < length { + val, err := rand.Int(rand.Reader, passwordCharSelector) // if there is an error generating the random integer, return if err != nil { return "", err } - password[i] = byte(passwordCharLower + char.Int64()) + char := byte(passwordCharLower + val.Int64()) + + // if the character is in the exclusion list, continue + if idx := strings.IndexAny(string(char), passwordCharExclude); idx > -1 { + continue + } + + password[i] = char + i++ } return string(password), nil diff --git a/internal/util/secrets_test.go b/internal/util/secrets_test.go index 89cbcebac9..423beb5e03 100644 --- a/internal/util/secrets_test.go +++ b/internal/util/secrets_test.go @@ -23,7 +23,7 @@ import ( func TestGeneratePassword(t *testing.T) { // different lengths - for _, length := range []int{1, 2, 3, 5, 20} { + for _, length := range []int{1, 2, 3, 5, 20, 200} { password, err := GeneratePassword(length) if err != nil { t.Fatalf("expected no error, got %v", err) @@ -31,9 +31,12 @@ func TestGeneratePassword(t *testing.T) { if expected, actual := length, len(password); expected != actual { t.Fatalf("expected length %v, got %v", expected, actual) } - if i := strings.IndexFunc(password, unicode.IsPrint); i > 0 { + if i := strings.IndexFunc(password, func(r rune) bool { return !unicode.IsPrint(r) }); i > -1 { t.Fatalf("expected only printable characters, got %q in %q", password[i], password) } + if i := strings.IndexAny(password, passwordCharExclude); i > -1 { + t.Fatalf("expected no exclude characters, got %q in %q", password[i], password) + } } // random contents @@ -44,9 +47,12 @@ func TestGeneratePassword(t *testing.T) { if err != nil { t.Fatalf("expected no error, got %v", err) } - if i := strings.IndexFunc(password, unicode.IsPrint); i > 0 { + if i := strings.IndexFunc(password, func(r rune) bool { return !unicode.IsPrint(r) }); i > -1 { t.Fatalf("expected only printable characters, got %q in %q", password[i], password) } + if i := strings.IndexAny(password, passwordCharExclude); i > -1 { + t.Fatalf("expected no exclude characters, got %q in %q", password[i], password) + } for i := range previous { if password == previous[i] { From 02a53114d7906a85ade3154c158e90674ea975a5 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sat, 26 Dec 2020 15:22:19 -0500 Subject: [PATCH 074/276] Ensure "hack" directory is skipped for linter Given the nature of this directory, let alone the name, we do not need to concern ourselves with the lint state of it. --- .golangci.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.golangci.yaml b/.golangci.yaml index 9918bb1a8b..937735ce02 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -18,4 +18,5 @@ linters-settings: run: skip-dirs: + - hack - pkg/generated From 82718f00a2f0171e48ba2d8aa0e9689277274a5f Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 27 Dec 2020 16:29:18 -0500 Subject: [PATCH 075/276] Remove reference to deprecated CRD attribute "ArchiveStorage" is no longer used, so there is no need to continue to carry this CRD attribute forward. --- examples/create-by-resource/fromcrd.json | 9 --------- pkg/apis/crunchydata.com/v1/cluster.go | 1 - pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go | 1 - 3 files changed, 11 deletions(-) diff --git a/examples/create-by-resource/fromcrd.json b/examples/create-by-resource/fromcrd.json index ea603808ac..58dd2f515e 100644 --- a/examples/create-by-resource/fromcrd.json +++ b/examples/create-by-resource/fromcrd.json @@ -24,15 +24,6 @@ "namespace": "pgouser1" }, "spec": { - "ArchiveStorage": { - "accessmode": "", - "matchLabels": "", - "name": "", - "size": "", - "storageclass": "", - "storagetype": "", - "supplementalgroups": "" - }, "BackrestStorage": { "accessmode": "ReadWriteOnce", "matchLabels": "", diff --git a/pkg/apis/crunchydata.com/v1/cluster.go b/pkg/apis/crunchydata.com/v1/cluster.go index fb121ea835..e2180e90dc 100644 --- a/pkg/apis/crunchydata.com/v1/cluster.go +++ b/pkg/apis/crunchydata.com/v1/cluster.go @@ -56,7 +56,6 @@ type PgclusterSpec struct { PrimaryStorage PgStorageSpec WALStorage PgStorageSpec - ArchiveStorage PgStorageSpec ReplicaStorage PgStorageSpec BackrestStorage PgStorageSpec diff --git a/pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go b/pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go index 80fd389e4f..69a3a673f5 100644 --- a/pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go +++ b/pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go @@ -196,7 +196,6 @@ func (in *PgclusterSpec) DeepCopyInto(out *PgclusterSpec) { *out = *in out.PrimaryStorage = in.PrimaryStorage out.WALStorage = in.WALStorage - out.ArchiveStorage = in.ArchiveStorage out.ReplicaStorage = in.ReplicaStorage out.BackrestStorage = in.BackrestStorage if in.Resources != nil { From 72281ca2dd535ec3ebc26833aefe63f95664a578 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 28 Dec 2020 10:17:58 -0500 Subject: [PATCH 076/276] Remove crunchy-admin container references This container was added provisionally in 4.2.0 to be used as part of the authentication and administration scheme for the Operator. However, this never was fully built out due to a variety of reasons, and as such in 4.6.0 this is being abandoned. A file found in `/crunchyadm/pgha_initialized` is still being used as one of the Operator's readiness checks, and as such, this directory is being kept for the time being. Additionally, a check for the "system account" named `crunchyadm` is still performed, as some legacy systems may still have this account in their PostgreSQL instantiation. Issue: [ch10007] --- docs/content/advanced/custom-configuration.md | 3 +- docs/content/pgo-client/common-tasks.md | 1 - examples/custom-config/postgres-ha.yaml | 3 +- .../roles/pgo-operator/defaults/main.yml | 1 - .../pgo-configs/cluster-bootstrap-job.json | 10 ----- .../files/pgo-configs/cluster-deployment.json | 41 +------------------ .../roles/pgo-operator/templates/pgo.yaml.j2 | 1 - .../olm/postgresoperator.csv.images.yaml | 1 - internal/config/images.go | 2 - internal/config/pgoconfig.go | 1 - internal/operator/cluster/clusterlogic.go | 2 - internal/operator/clusterutilities.go | 8 +--- internal/operator/clusterutilities_test.go | 9 ++-- pkg/apis/crunchydata.com/v1/common.go | 4 +- testing/pgo_cli/cluster_restart_test.go | 2 +- 15 files changed, 12 insertions(+), 77 deletions(-) diff --git a/docs/content/advanced/custom-configuration.md b/docs/content/advanced/custom-configuration.md index 4ac15d1578..5a7e4f0e34 100644 --- a/docs/content/advanced/custom-configuration.md +++ b/docs/content/advanced/custom-configuration.md @@ -150,7 +150,7 @@ postgresql: shared_buffers: 128MB shared_preload_libraries: pgaudit.so,pg_stat_statements.so temp_buffers: 8MB - unix_socket_directories: /tmp,/crunchyadm + unix_socket_directories: /tmp wal_level: logical work_mem: 4MB recovery_conf: @@ -168,7 +168,6 @@ postgresql: - basebackup pg_hba: - local all postgres peer - - local all crunchyadm peer - host replication primaryuser 0.0.0.0/0 md5 - host all primaryuser 0.0.0.0/0 reject - host all all 0.0.0.0/0 md5 diff --git a/docs/content/pgo-client/common-tasks.md b/docs/content/pgo-client/common-tasks.md index 58e7476dfa..02243ea62f 100644 --- a/docs/content/pgo-client/common-tasks.md +++ b/docs/content/pgo-client/common-tasks.md @@ -128,7 +128,6 @@ Cluster: BackrestS3URIStyle: "" BackrestS3VerifyTLS: true DisableAutofail: false - EnableCrunchyadm: false DisableReplicaStartFailReinit: false PodAntiAffinity: preferred SyncReplication: false diff --git a/examples/custom-config/postgres-ha.yaml b/examples/custom-config/postgres-ha.yaml index 0f4cd6fbab..5d823d4a81 100644 --- a/examples/custom-config/postgres-ha.yaml +++ b/examples/custom-config/postgres-ha.yaml @@ -12,10 +12,9 @@ bootstrap: shared_buffers: 256MB temp_buffers: 10MB work_mem: 5MB -postgresql: +postgresql: pg_hba: - local all postgres peer - - local all crunchyadm peer - host replication primaryuser 0.0.0.0/0 md5 - host all primaryuser 0.0.0.0/0 reject - host all postgres 0.0.0.0/0 md5 diff --git a/installers/ansible/roles/pgo-operator/defaults/main.yml b/installers/ansible/roles/pgo-operator/defaults/main.yml index 39fb88c679..fb41fa471e 100644 --- a/installers/ansible/roles/pgo-operator/defaults/main.yml +++ b/installers/ansible/roles/pgo-operator/defaults/main.yml @@ -16,7 +16,6 @@ service_type: "ClusterIP" cleanup: "false" common_name: "crunchydata" crunchy_debug: "false" -enable_crunchyadm: "false" disable_replica_start_fail_reinit: "false" disable_fsgroup: "false" diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json index 9bd5a10f21..ee5e5307a9 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json @@ -57,9 +57,6 @@ { "name": "PGHA_DATABASE", "value": "{{.Database}}" - }, { - "name": "PGHA_CRUNCHYADM", - "value": "true" }, { "name": "PGHA_REPLICA_REINIT_ON_START_FAIL", "value": "{{.ReplicaReinitOnStartFail}}" @@ -137,9 +134,6 @@ }, { "mountPath": "/etc/pgbackrest/conf.d", "name": "pgbackrest-config" - }, { - "mountPath": "/crunchyadm", - "name": "crunchyadm" } {{.TablespaceVolumeMounts}} ], @@ -189,10 +183,6 @@ } }, {{ end }} - { - "name": "crunchyadm", - "emptyDir": {} - }, { "name": "dshm", "emptyDir": { diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json index c05ee7210c..33c02937c4 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json @@ -95,9 +95,6 @@ { "name": "PGHA_DATABASE", "value": "{{.Database}}" - }, { - "name": "PGHA_CRUNCHYADM", - "value": "true" }, { "name": "PGHA_REPLICA_REINIT_ON_START_FAIL", "value": "{{.ReplicaReinitOnStartFail}}" @@ -187,10 +184,6 @@ "mountPath": "/etc/pgbackrest/conf.d", "name": "pgbackrest-config" }, - { - "mountPath": "/crunchyadm", - "name": "crunchyadm" - }, { "mountPath": "/etc/podinfo", "name": "podinfo" @@ -206,36 +199,7 @@ "protocol": "TCP" }], "imagePullPolicy": "IfNotPresent" - }{{if .EnableCrunchyadm}}, - { - "name": "crunchyadm", - "image": "{{.CCPImagePrefix}}/crunchy-admin:{{.CCPImageTag}}", - "securityContext": { - "runAsUser": 17 - }, - "readinessProbe": { - "exec": { - "command": [ - "/opt/cpm/bin/crunchyadm-readiness.sh" - ] - }, - "initialDelaySeconds": 30, - "timeoutSeconds": 10 - }, - "env": [ - { - "name": "PGHOST", - "value": "/crunchyadm" - } - ], - "volumeMounts": [ - { - "mountPath": "/crunchyadm", - "name": "crunchyadm" - } - ], - "imagePullPolicy": "IfNotPresent" - }{{ end }} + } {{ if .ExporterAddon }} ,{{.ExporterAddon }} {{ end }} @@ -319,9 +283,6 @@ }, { "name": "report", "emptyDir": { "medium": "Memory" } - }, { - "name": "crunchyadm", - "emptyDir": {} }, { "name": "dshm", diff --git a/installers/ansible/roles/pgo-operator/templates/pgo.yaml.j2 b/installers/ansible/roles/pgo-operator/templates/pgo.yaml.j2 index f1b21fbbcb..e87a245bed 100644 --- a/installers/ansible/roles/pgo-operator/templates/pgo.yaml.j2 +++ b/installers/ansible/roles/pgo-operator/templates/pgo.yaml.j2 @@ -20,7 +20,6 @@ Cluster: Replicas: {{ db_replicas }} ArchiveMode: {{ archive_mode }} ServiceType: {{ service_type }} - EnableCrunchyadm: {{ enable_crunchyadm }} DisableReplicaStartFailReinit: {{ disable_replica_start_fail_reinit }} PodAntiAffinity: {{ pod_anti_affinity }} PodAntiAffinityPgBackRest: {{ pod_anti_affinity_pgbackrest }} diff --git a/installers/olm/postgresoperator.csv.images.yaml b/installers/olm/postgresoperator.csv.images.yaml index 21d1f3c10f..c8f3a386d6 100644 --- a/installers/olm/postgresoperator.csv.images.yaml +++ b/installers/olm/postgresoperator.csv.images.yaml @@ -10,7 +10,6 @@ - { name: RELATED_IMAGE_PGO_RMDATA, value: '${PGO_IMAGE_PREFIX}/pgo-rmdata:${PGO_IMAGE_TAG}' } - { name: RELATED_IMAGE_CRUNCHY_POSTGRES_EXPORTER, value: '${PGO_IMAGE_PREFIX}/crunchy-postgres-exporter:${PGO_IMAGE_TAG}' } -- { name: RELATED_IMAGE_CRUNCHY_ADMIN, value: '${CCP_IMAGE_PREFIX}/crunchy-admin:${CCP_IMAGE_TAG}' } - { name: RELATED_IMAGE_CRUNCHY_PGADMIN, value: '${CCP_IMAGE_PREFIX}/crunchy-pgadmin4:${CCP_IMAGE_TAG}' } - { name: RELATED_IMAGE_CRUNCHY_PGBADGER, value: '${CCP_IMAGE_PREFIX}/crunchy-pgbadger:${CCP_IMAGE_TAG}' } - { name: RELATED_IMAGE_CRUNCHY_PGBOUNCER, value: '${CCP_IMAGE_PREFIX}/crunchy-pgbouncer:${CCP_IMAGE_TAG}' } diff --git a/internal/config/images.go b/internal/config/images.go index 3c7fdf4285..7ab595ed98 100644 --- a/internal/config/images.go +++ b/internal/config/images.go @@ -21,7 +21,6 @@ const ( CONTAINER_IMAGE_PGO_BACKREST_REPO = "crunchy-pgbackrest-repo" CONTAINER_IMAGE_PGO_CLIENT = "pgo-client" CONTAINER_IMAGE_PGO_RMDATA = "pgo-rmdata" - CONTAINER_IMAGE_CRUNCHY_ADMIN = "crunchy-admin" CONTAINER_IMAGE_CRUNCHY_POSTGRES_EXPORTER = "crunchy-postgres-exporter" CONTAINER_IMAGE_CRUNCHY_GRAFANA = "crunchy-grafana" CONTAINER_IMAGE_CRUNCHY_PGADMIN = "crunchy-pgadmin4" @@ -42,7 +41,6 @@ var RelatedImageMap = map[string]string{ "RELATED_IMAGE_PGO_BACKREST_REPO": CONTAINER_IMAGE_PGO_BACKREST_REPO, "RELATED_IMAGE_PGO_CLIENT": CONTAINER_IMAGE_PGO_CLIENT, "RELATED_IMAGE_PGO_RMDATA": CONTAINER_IMAGE_PGO_RMDATA, - "RELATED_IMAGE_CRUNCHY_ADMIN": CONTAINER_IMAGE_CRUNCHY_ADMIN, "RELATED_IMAGE_CRUNCHY_POSTGRES_EXPORTER": CONTAINER_IMAGE_CRUNCHY_POSTGRES_EXPORTER, "RELATED_IMAGE_CRUNCHY_PGADMIN": CONTAINER_IMAGE_CRUNCHY_PGADMIN, "RELATED_IMAGE_CRUNCHY_PGBADGER": CONTAINER_IMAGE_CRUNCHY_PGBADGER, diff --git a/internal/config/pgoconfig.go b/internal/config/pgoconfig.go index c073d9c43f..6c870686a0 100644 --- a/internal/config/pgoconfig.go +++ b/internal/config/pgoconfig.go @@ -213,7 +213,6 @@ type ClusterStruct struct { BackrestS3URIStyle string BackrestS3VerifyTLS string DisableAutofail bool - EnableCrunchyadm bool DisableReplicaStartFailReinit bool PodAntiAffinity string PodAntiAffinityPgBackRest string diff --git a/internal/operator/cluster/clusterlogic.go b/internal/operator/cluster/clusterlogic.go index 3af8b13729..2a779614d2 100644 --- a/internal/operator/cluster/clusterlogic.go +++ b/internal/operator/cluster/clusterlogic.go @@ -329,7 +329,6 @@ func getClusterDeploymentFields(clientset kubernetes.Interface, PgbackrestEnvVars: operator.GetPgbackrestEnvVars(cl, cl.Labels[config.LABEL_BACKREST], cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY], cl.Spec.Port, cl.Spec.UserLabels[config.LABEL_BACKREST_STORAGE_TYPE]), PgbackrestS3EnvVars: operator.GetPgbackrestS3EnvVars(*cl, clientset, namespace), - EnableCrunchyadm: operator.Pgo.Cluster.EnableCrunchyadm, ReplicaReinitOnStartFail: !operator.Pgo.Cluster.DisableReplicaStartFailReinit, SyncReplication: operator.GetSyncReplication(cl.Spec.SyncReplication), Tablespaces: operator.GetTablespaceNames(cl.Spec.TablespaceMounts), @@ -485,7 +484,6 @@ func scaleReplicaCreateDeployment(clientset kubernetes.Interface, PgbackrestEnvVars: operator.GetPgbackrestEnvVars(cluster, cluster.Labels[config.LABEL_BACKREST], replica.Spec.Name, cluster.Spec.Port, cluster.Spec.UserLabels[config.LABEL_BACKREST_STORAGE_TYPE]), PgbackrestS3EnvVars: operator.GetPgbackrestS3EnvVars(*cluster, clientset, namespace), - EnableCrunchyadm: operator.Pgo.Cluster.EnableCrunchyadm, ReplicaReinitOnStartFail: !operator.Pgo.Cluster.DisableReplicaStartFailReinit, SyncReplication: operator.GetSyncReplication(cluster.Spec.SyncReplication), Tablespaces: operator.GetTablespaceNames(cluster.Spec.TablespaceMounts), diff --git a/internal/operator/clusterutilities.go b/internal/operator/clusterutilities.go index 776c264579..f96d5e5832 100644 --- a/internal/operator/clusterutilities.go +++ b/internal/operator/clusterutilities.go @@ -178,7 +178,6 @@ type DeploymentTemplateFields struct { ScopeLabel string Replicas string IsInit bool - EnableCrunchyadm bool ReplicaReinitOnStartFail bool PodAntiAffinity string SyncReplication bool @@ -978,15 +977,12 @@ func OverrideClusterContainerImages(containers []v1.Container) { var containerImageName string // there are a few images we need to check for: // 1. "database" image, which is PostgreSQL or some flavor of it - // 2. "crunchyadm" image, which helps with administration - // 3. "exporter" image, which helps with monitoring - // 4. "pgbadger" image, which helps with...pgbadger + // 2. "exporter" image, which helps with monitoring + // 3. "pgbadger" image, which helps with...pgbadger switch container.Name { case "exporter": containerImageName = config.CONTAINER_IMAGE_CRUNCHY_POSTGRES_EXPORTER - case "crunchyadm": - containerImageName = config.CONTAINER_IMAGE_CRUNCHY_ADMIN case "database": containerImageName = config.CONTAINER_IMAGE_CRUNCHY_POSTGRES_HA // one more step here...determine if this is GIS enabled diff --git a/internal/operator/clusterutilities_test.go b/internal/operator/clusterutilities_test.go index 7a3643f777..52e31aa66c 100644 --- a/internal/operator/clusterutilities_test.go +++ b/internal/operator/clusterutilities_test.go @@ -127,11 +127,10 @@ func TestOverrideClusterContainerImages(t *testing.T) { name string image string }{ - "database": {name: "database", image: config.CONTAINER_IMAGE_CRUNCHY_POSTGRES_HA}, - "crunchyadm": {name: "crunchyadm", image: config.CONTAINER_IMAGE_CRUNCHY_ADMIN}, - "exporter": {name: "exporter", image: config.CONTAINER_IMAGE_CRUNCHY_POSTGRES_EXPORTER}, - "pgbadger": {name: "pgbadger", image: config.CONTAINER_IMAGE_CRUNCHY_PGBADGER}, - "future": {name: "future", image: "crunchy-future"}, + "database": {name: "database", image: config.CONTAINER_IMAGE_CRUNCHY_POSTGRES_HA}, + "exporter": {name: "exporter", image: config.CONTAINER_IMAGE_CRUNCHY_POSTGRES_EXPORTER}, + "pgbadger": {name: "pgbadger", image: config.CONTAINER_IMAGE_CRUNCHY_PGBADGER}, + "future": {name: "future", image: "crunchy-future"}, } t.Run("no override", func(t *testing.T) { diff --git a/pkg/apis/crunchydata.com/v1/common.go b/pkg/apis/crunchydata.com/v1/common.go index 6bf14408dc..fcd2238f36 100644 --- a/pkg/apis/crunchydata.com/v1/common.go +++ b/pkg/apis/crunchydata.com/v1/common.go @@ -46,8 +46,8 @@ const StorageDynamic = "dynamic" // the following are standard PostgreSQL user service accounts that are created // as part of managed the PostgreSQL cluster environment via the Operator const ( - // PGUserAdmin is a special user that can perform administrative actions - // without being a superuser itself + // PGUserAdmin is a DEPRECATED user and is only included to filter this out + // as a system user in older systems PGUserAdmin = "crunchyadm" // PGUserMonitor is the monitoring user that can access metric data PGUserMonitor = "ccp_monitoring" diff --git a/testing/pgo_cli/cluster_restart_test.go b/testing/pgo_cli/cluster_restart_test.go index 3f438b6570..9daeea644e 100644 --- a/testing/pgo_cli/cluster_restart_test.go +++ b/testing/pgo_cli/cluster_restart_test.go @@ -114,7 +114,7 @@ func TestRestart(t *testing.T) { // now update a PG setting updatePGConfigDCS(t, cluster(), namespace(), - map[string]string{"unix_socket_directories": "/tmp,/crunchyadm,/tmp/e2e"}) + map[string]string{"unix_socket_directories": "/tmp,/tmp/e2e"}) requiresRestartPrimaryReplica := func() bool { output, err := pgo(restartQueryCMD...).Exec(t) From 43aef7613b31268edc9267a42aed22102012260b Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 28 Dec 2020 10:28:05 -0500 Subject: [PATCH 077/276] Move TLS key generation to ECDSA This moves the default TLS key generation for the API server to use ECDSA keys with a P-256 curve and a SHA384 signature. This only affects newly created Operator deployments, or Operator deployments that have deleted their TLS secret. --- deploy/gen-api-keys.sh | 32 ++++++------------- .../roles/pgo-operator/tasks/certs.yml | 30 +++++------------ internal/apiserver/root.go | 4 +-- internal/tlsutil/primitives.go | 22 +++++++------ internal/tlsutil/primitives_test.go | 30 +---------------- 5 files changed, 32 insertions(+), 86 deletions(-) diff --git a/deploy/gen-api-keys.sh b/deploy/gen-api-keys.sh index 8aece10000..15b310f85f 100755 --- a/deploy/gen-api-keys.sh +++ b/deploy/gen-api-keys.sh @@ -13,28 +13,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -# # generate self signed cert for apiserver REST service -# - openssl req \ --x509 \ --nodes \ --newkey rsa:2048 \ --keyout $PGOROOT/conf/postgres-operator/server.key \ --out $PGOROOT/conf/postgres-operator/server.crt \ --days 3650 \ --subj "/C=US/ST=Texas/L=Austin/O=TestOrg/OU=TestDepartment/CN=*" - -# generate CA -#openssl genrsa -out $PGOROOT/conf/apiserver/rootCA.key 4096 -#openssl req -x509 -new -key $PGOROOT/conf/apiserver/rootCA.key -days 3650 -out $PGOROOT/conf/apiserver/rootCA.crt - -# generate cert for secure.domain.com signed with the created CA -#openssl genrsa -out $PGOROOT/conf/apiserver/secure.domain.com.key 2048 -#openssl req -new -key $PGOROOT/conf/apiserver/secure.domain.com.key -out $PGOROOT/conf/apiserver/secure.domain.com.csr -#In answer to question `Common Name (e.g. server FQDN or YOUR name) []:` you should set `secure.domain.com` (your real domain name) -#openssl x509 -req -in $PGOROOT/conf/apiserver/secure.domain.com.csr -CA $PGOROOT/conf/apiserver/rootCA.crt -CAkey $PGOROOT/conf/apiserver/rootCA.key -CAcreateserial -days 365 -out $PGOROOT/conf/apiserver/secure.domain.com.crt - -#openssl genrsa 2048 > $PGOROOT/conf/apiserver/key.pem -#openssl req -new -x509 -key $PGOROOT/conf/apiserver/key.pem -out $PGOROOT/conf/apiserver/cert.pem -days 1095 + -x509 \ + -nodes \ + -newkey ec \ + -pkeyopt ec_paramgen_curve:prime256v1 \ + -sha384 \ + -keyout $PGOROOT/conf/postgres-operator/server.key \ + -out $PGOROOT/conf/postgres-operator/server.crt \ + -days 3650 \ + -subj "/CN=*" diff --git a/installers/ansible/roles/pgo-operator/tasks/certs.yml b/installers/ansible/roles/pgo-operator/tasks/certs.yml index 4c66e89892..07e3077eee 100644 --- a/installers/ansible/roles/pgo-operator/tasks/certs.yml +++ b/installers/ansible/roles/pgo-operator/tasks/certs.yml @@ -6,33 +6,19 @@ tags: - install -- name: Generate RSA Key - command: openssl genrsa -out "{{ output_dir }}/server.key" 2048 - args: - creates: "{{ output_dir }}/server.key" - tags: - - install - -- name: Generate CSR - command: openssl req \ - -new \ - -subj '/C=US/ST=SC/L=Charleston/O=CrunchyData/CN=pg-operator' \ - -key "{{ output_dir }}/server.key" \ - -out "{{ output_dir }}/server.csr" - args: - creates: "{{ output_dir }}/server.csr" - tags: - - install - - name: Generate Self-signed Certificate command: openssl req \ -x509 \ + -nodes \ + -newkey ec \ + -pkeyopt ec_paramgen_curve:prime256v1 \ + -sha384 \ -days 1825 \ - -key "{{ output_dir }}/server.key" \ - -in "{{ output_dir }}/server.csr" \ - -out "{{ output_dir }}/server.crt" + -subj "/CN=*" \ + -keyout {{ output_dir }}/server.key \ + -out {{ output_dir }}/server.crt args: - creates: "{{ output_dir }}/server.crt" + creates: "{{ output_dir }}/server.[kc][er][yt]" tags: - install diff --git a/internal/apiserver/root.go b/internal/apiserver/root.go index 769ee79ab7..bf68f1b870 100644 --- a/internal/apiserver/root.go +++ b/internal/apiserver/root.go @@ -17,7 +17,7 @@ limitations under the License. import ( "context" - "crypto/rsa" + "crypto/ecdsa" "crypto/x509" "errors" "fmt" @@ -438,7 +438,7 @@ func generateTLSCert(certPath, keyPath string) error { var err error // generate private key - var privateKey *rsa.PrivateKey + var privateKey *ecdsa.PrivateKey privateKey, err = tlsutil.NewPrivateKey() if err != nil { fmt.Println(err.Error()) diff --git a/internal/tlsutil/primitives.go b/internal/tlsutil/primitives.go index 03fb73f744..2ed4881e8e 100644 --- a/internal/tlsutil/primitives.go +++ b/internal/tlsutil/primitives.go @@ -16,8 +16,9 @@ limitations under the License. */ import ( + "crypto/ecdsa" + "crypto/elliptic" "crypto/rand" - "crypto/rsa" "crypto/x509" "encoding/pem" "errors" @@ -29,20 +30,20 @@ import ( ) const ( - rsaKeySize = 2048 duration365d = time.Hour * 24 * 365 ) // newPrivateKey returns randomly generated RSA private key. -func NewPrivateKey() (*rsa.PrivateKey, error) { - return rsa.GenerateKey(rand.Reader, rsaKeySize) +func NewPrivateKey() (*ecdsa.PrivateKey, error) { + return ecdsa.GenerateKey(elliptic.P256(), rand.Reader) } // encodePrivateKeyPEM encodes the given private key pem and returns bytes (base64). -func EncodePrivateKeyPEM(key *rsa.PrivateKey) []byte { +func EncodePrivateKeyPEM(key *ecdsa.PrivateKey) []byte { + raw, _ := x509.MarshalECPrivateKey(key) return pem.EncodeToMemory(&pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(key), + Type: "EC PRIVATE KEY", + Bytes: raw, }) } @@ -64,17 +65,17 @@ func ParsePEMEncodedCert(pemdata []byte) (*x509.Certificate, error) { } // parsePEMEncodedPrivateKey parses a private key from given pemdata -func ParsePEMEncodedPrivateKey(pemdata []byte) (*rsa.PrivateKey, error) { +func ParsePEMEncodedPrivateKey(pemdata []byte) (*ecdsa.PrivateKey, error) { decoded, _ := pem.Decode(pemdata) if decoded == nil { return nil, errors.New("no PEM data found") } - return x509.ParsePKCS1PrivateKey(decoded.Bytes) + return x509.ParseECPrivateKey(decoded.Bytes) } // newSelfSignedCACertificate returns a self-signed CA certificate based on given configuration and private key. // The certificate has one-year lease. -func NewSelfSignedCACertificate(key *rsa.PrivateKey) (*x509.Certificate, error) { +func NewSelfSignedCACertificate(key *ecdsa.PrivateKey) (*x509.Certificate, error) { serial, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64)) if err != nil { return nil, err @@ -87,6 +88,7 @@ func NewSelfSignedCACertificate(key *rsa.PrivateKey) (*x509.Certificate, error) KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, BasicConstraintsValid: true, IsCA: true, + SignatureAlgorithm: x509.ECDSAWithSHA384, } certDERBytes, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, key.Public(), key) if err != nil { diff --git a/internal/tlsutil/primitives_test.go b/internal/tlsutil/primitives_test.go index 1c6538f543..09d4dab6ce 100644 --- a/internal/tlsutil/primitives_test.go +++ b/internal/tlsutil/primitives_test.go @@ -18,7 +18,6 @@ limitations under the License. import ( "bytes" "context" - "crypto/rsa" "crypto/tls" "crypto/x509" "encoding/base64" @@ -43,7 +42,7 @@ func TestKeyPEMSymmetry(t *testing.T) { t.Log(base64.StdEncoding.EncodeToString(pemKey)) - if !keysEq(oldKey, newKey) { + if !(oldKey.Equal(newKey) && oldKey.PublicKey.Equal(newKey.Public())) { t.Fatal("Decoded key did not match its input source") } } @@ -145,30 +144,3 @@ func TestExtendedTrust(t *testing.T) { t.Fatalf("expected [%s], got [%s] instead\n", expected, recv) } } - -func keysEq(a, b *rsa.PrivateKey) bool { - if a.E != b.E { - // PublicKey exponent different - return false - } - if a.N.Cmp(b.N) != 0 { - // PublicKey modulus different - return false - } - if a.D.Cmp(b.D) != 0 { - // PrivateKey exponent different - return false - } - if len(a.Primes) != len(b.Primes) { - // Prime factor difference (Tier 1) - return false - } - for i, aPrime := range a.Primes { - if aPrime.Cmp(b.Primes[i]) != 0 { - // Prime factor difference (Tier 2) - return false - } - } - - return true -} From 68e638446dd3e2265214f4b7771baa8d29327f30 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Thu, 24 Dec 2020 17:41:30 -0500 Subject: [PATCH 078/276] Rewrite `pgo scale` endpoint to accept POST requests This rewrites the `pgo scale` API endpoint (/custers/scale/{name}) to accept POST instead of GET requests, and moves all of the parameters to the POST body. However, the "{name}" parameter in the URL path still takes precedence in reference to the cluster name. This change allows for more complex parameters to be passed into the scale request, as well as moves the request itself to something that is closer to the REST convention. --- cmd/pgo/api/scale.go | 38 ++---- cmd/pgo/cmd/scale.go | 29 ++-- .../apiserver/clusterservice/scaleimpl.go | 53 +++---- .../apiserver/clusterservice/scaleservice.go | 129 ++++++++---------- internal/apiserver/routing/routes.go | 2 +- pkg/apiservermsgs/clustermsgs.go | 28 ++++ 6 files changed, 141 insertions(+), 138 deletions(-) diff --git a/cmd/pgo/api/scale.go b/cmd/pgo/api/scale.go index 87eae783f3..574cbc8b4c 100644 --- a/cmd/pgo/api/scale.go +++ b/cmd/pgo/api/scale.go @@ -16,56 +16,48 @@ package api */ import ( + "bytes" "context" "encoding/json" "fmt" "net/http" - "strconv" msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" ) -func ScaleCluster(httpclient *http.Client, arg string, ReplicaCount int, - StorageConfig, NodeLabel, CCPImageTag, ServiceType string, - SessionCredentials *msgs.BasicAuthCredentials, ns string) (msgs.ClusterScaleResponse, error) { - var response msgs.ClusterScaleResponse +func ScaleCluster(httpclient *http.Client, SessionCredentials *msgs.BasicAuthCredentials, + request msgs.ClusterScaleRequest) (msgs.ClusterScaleResponse, error) { + response := msgs.ClusterScaleResponse{} + ctx := context.TODO() + request.ClientVersion = msgs.PGO_VERSION - url := fmt.Sprintf("%s/clusters/scale/%s", SessionCredentials.APIServerURL, arg) - log.Debug(url) + url := fmt.Sprintf("%s/clusters/scale/%s", SessionCredentials.APIServerURL, request.Name) + jsonValue, _ := json.Marshal(request) - ctx := context.TODO() - action := "GET" - req, err := http.NewRequestWithContext(ctx, action, url, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonValue)) if err != nil { return response, err } - q := req.URL.Query() - q.Add("replica-count", strconv.Itoa(ReplicaCount)) - q.Add("storage-config", StorageConfig) - q.Add("node-label", NodeLabel) - q.Add("version", msgs.PGO_VERSION) - q.Add("ccp-image-tag", CCPImageTag) - q.Add("service-type", ServiceType) - q.Add("namespace", ns) - req.URL.RawQuery = q.Encode() - + req.Header.Set("Content-Type", "application/json") req.SetBasicAuth(SessionCredentials.Username, SessionCredentials.Password) resp, err := httpclient.Do(req) if err != nil { return response, err } + defer resp.Body.Close() + log.Debugf("%v", resp) - err = StatusCheck(resp) - if err != nil { + + if err := StatusCheck(resp); err != nil { return response, err } if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { - log.Printf("%v\n", resp.Body) + log.Debugf("%+v", resp.Body) fmt.Println("Error: ", err) log.Println(err) return response, err diff --git a/cmd/pgo/cmd/scale.go b/cmd/pgo/cmd/scale.go index 0fd9bbdb16..d74a38f035 100644 --- a/cmd/pgo/cmd/scale.go +++ b/cmd/pgo/cmd/scale.go @@ -58,29 +58,38 @@ func init() { scaleCmd.Flags().StringVarP(&ServiceType, "service-type", "", "", "The service type to use in the replica Service. If not set, the default in pgo.yaml will be used.") scaleCmd.Flags().StringVarP(&CCPImageTag, "ccp-image-tag", "", "", "The CCPImageTag to use for cluster creation. If specified, overrides the .pgo.yaml setting.") + scaleCmd.Flags().StringVarP(&NodeLabel, "node-label", "", "", "The node label (key) to use in placing the replica database. If not set, any node is used.") scaleCmd.Flags().BoolVar(&NoPrompt, "no-prompt", false, "No command line confirmation.") scaleCmd.Flags().IntVarP(&ReplicaCount, "replica-count", "", 1, "The replica count to apply to the clusters.") scaleCmd.Flags().StringVarP(&StorageConfig, "storage-config", "", "", "The name of a Storage config in pgo.yaml to use for the replica storage.") - scaleCmd.Flags().StringVarP(&NodeLabel, "node-label", "", "", "The node label (key) to use in placing the replica database. If not set, any node is used.") } func scaleCluster(args []string, ns string) { for _, arg := range args { - log.Debugf(" %s ReplicaCount is %d", arg, ReplicaCount) - response, err := api.ScaleCluster(httpclient, arg, ReplicaCount, - StorageConfig, NodeLabel, CCPImageTag, ServiceType, &SessionCredentials, ns) + request := msgs.ClusterScaleRequest{ + CCPImageTag: CCPImageTag, + Name: arg, + Namespace: ns, + NodeLabel: NodeLabel, + ReplicaCount: ReplicaCount, + ServiceType: ServiceType, + StorageConfig: StorageConfig, + } + + response, err := api.ScaleCluster(httpclient, &SessionCredentials, request) + if err != nil { fmt.Println("Error: " + err.Error()) - os.Exit(2) + os.Exit(1) } - if response.Status.Code == msgs.Ok { - for _, v := range response.Results { - fmt.Println(v) - } - } else { + if response.Status.Code != msgs.Ok { fmt.Println("Error: " + response.Status.Msg) + os.Exit(1) } + for _, v := range response.Results { + fmt.Println(v) + } } } diff --git a/internal/apiserver/clusterservice/scaleimpl.go b/internal/apiserver/clusterservice/scaleimpl.go index 00be04242e..b8dadef637 100644 --- a/internal/apiserver/clusterservice/scaleimpl.go +++ b/internal/apiserver/clusterservice/scaleimpl.go @@ -18,7 +18,6 @@ limitations under the License. import ( "context" "fmt" - "strconv" "strings" "github.com/crunchydata/postgres-operator/internal/apiserver" @@ -33,21 +32,21 @@ import ( ) // ScaleCluster ... -func ScaleCluster(name, replicaCount, storageConfig, nodeLabel, - ccpImageTag, serviceType, ns, pgouser string) msgs.ClusterScaleResponse { +func ScaleCluster(request msgs.ClusterScaleRequest, pgouser string) msgs.ClusterScaleResponse { ctx := context.TODO() var err error response := msgs.ClusterScaleResponse{} response.Status = msgs.Status{Code: msgs.Ok, Msg: ""} - if name == "all" { + if request.ReplicaCount < 1 { + log.Error("replica count less than 1, no replicas added") response.Status.Code = msgs.Error - response.Status.Msg = "all is not allowed for the scale command" + response.Status.Msg = "replica count must be at least 1" return response } - cluster, err := apiserver.Clientset.CrunchydataV1().Pgclusters(ns).Get(ctx, name, metav1.GetOptions{}) + cluster, err := apiserver.Clientset.CrunchydataV1().Pgclusters(request.Namespace).Get(ctx, request.Name, metav1.GetOptions{}) if kerrors.IsNotFound(err) { log.Error("no clusters found") @@ -77,24 +76,24 @@ func ScaleCluster(name, replicaCount, storageConfig, nodeLabel, spec.ReplicaStorage = cluster.Spec.ReplicaStorage // allow for user override - if storageConfig != "" { - spec.ReplicaStorage, _ = apiserver.Pgo.GetStorageSpec(storageConfig) + if request.StorageConfig != "" { + spec.ReplicaStorage, _ = apiserver.Pgo.GetStorageSpec(request.StorageConfig) } spec.UserLabels = cluster.Spec.UserLabels - if ccpImageTag != "" { - spec.UserLabels[config.LABEL_CCP_IMAGE_TAG_KEY] = ccpImageTag + if request.CCPImageTag != "" { + spec.UserLabels[config.LABEL_CCP_IMAGE_TAG_KEY] = request.CCPImageTag } - if serviceType != "" { - if serviceType != config.DEFAULT_SERVICE_TYPE && - serviceType != config.NODEPORT_SERVICE_TYPE && - serviceType != config.LOAD_BALANCER_SERVICE_TYPE { + if request.ServiceType != "" { + if request.ServiceType != config.DEFAULT_SERVICE_TYPE && + request.ServiceType != config.NODEPORT_SERVICE_TYPE && + request.ServiceType != config.LOAD_BALANCER_SERVICE_TYPE { response.Status.Code = msgs.Error response.Status.Msg = "error --service-type should be either ClusterIP, NodePort, or LoadBalancer " return response } - spec.UserLabels[config.LABEL_SERVICE_TYPE] = serviceType + spec.UserLabels[config.LABEL_SERVICE_TYPE] = request.ServiceType } // set replica node lables to blank to start with, then check for overrides @@ -102,14 +101,14 @@ func ScaleCluster(name, replicaCount, storageConfig, nodeLabel, spec.UserLabels[config.LABEL_NODE_LABEL_VALUE] = "" // validate & parse nodeLabel if exists - if nodeLabel != "" { - if err = apiserver.ValidateNodeLabel(nodeLabel); err != nil { + if request.NodeLabel != "" { + if err = apiserver.ValidateNodeLabel(request.NodeLabel); err != nil { response.Status.Code = msgs.Error response.Status.Msg = err.Error() return response } - parts := strings.Split(nodeLabel, "=") + parts := strings.Split(request.NodeLabel, "=") spec.UserLabels[config.LABEL_NODE_LABEL_KEY] = parts[0] spec.UserLabels[config.LABEL_NODE_LABEL_VALUE] = parts[1] @@ -121,23 +120,13 @@ func ScaleCluster(name, replicaCount, storageConfig, nodeLabel, spec.ClusterName = cluster.Spec.Name - var rc int - rc, err = strconv.Atoi(replicaCount) - if err != nil { - log.Error(err.Error()) - response.Status.Code = msgs.Error - response.Status.Msg = err.Error() - return response - } - labels[config.LABEL_PGOUSER] = pgouser labels[config.LABEL_PG_CLUSTER_IDENTIFIER] = cluster.ObjectMeta.Labels[config.LABEL_PG_CLUSTER_IDENTIFIER] - for i := 0; i < rc; i++ { - + for i := 0; i < request.ReplicaCount; i++ { uniqueName := util.RandStringBytesRmndr(4) labels[config.LABEL_NAME] = cluster.Spec.Name + "-" + uniqueName - spec.Namespace = ns + spec.Namespace = cluster.Namespace spec.Name = labels[config.LABEL_NAME] newInstance := &crv1.Pgreplica{ @@ -152,8 +141,8 @@ func ScaleCluster(name, replicaCount, storageConfig, nodeLabel, }, } - _, err = apiserver.Clientset.CrunchydataV1().Pgreplicas(ns).Create(ctx, newInstance, metav1.CreateOptions{}) - if err != nil { + if _, err := apiserver.Clientset.CrunchydataV1().Pgreplicas(cluster.Namespace).Create(ctx, + newInstance, metav1.CreateOptions{}); err != nil { log.Error(" in creating Pgreplica instance" + err.Error()) } diff --git a/internal/apiserver/clusterservice/scaleservice.go b/internal/apiserver/clusterservice/scaleservice.go index d0d54d6119..810eba4086 100644 --- a/internal/apiserver/clusterservice/scaleservice.go +++ b/internal/apiserver/clusterservice/scaleservice.go @@ -40,99 +40,84 @@ func ScaleClusterHandler(w http.ResponseWriter, r *http.Request) { // produces: // - application/json // parameters: - // - name: "name" - // description: "Cluster Name" - // in: "path" - // type: "string" - // required: true - // - name: "version" - // description: "Client Version" - // in: "path" - // type: "string" - // required: true - // - name: "namespace" - // description: "Namespace" - // in: "path" - // type: "string" - // required: true - // - name: "replica-count" - // description: "The replica count to apply to the clusters." - // in: "path" - // type: "int" - // required: true - // - name: "storage-config" - // description: "The service type to use in the replica Service. If not set, the default in pgo.yaml will be used." - // in: "path" - // type: "string" - // required: false - // - name: "node-label" - // description: "The node label (key) to use in placing the replica database. If not set, any node is used." - // in: "path" - // type: "string" - // required: false - // - name: "service-type" - // description: "The service type to use in the replica Service. If not set, the default in pgo.yaml will be used." - // in: "path" - // type: "string" - // required: false - // - name: "ccp-image-tag" - // description: "The CCPImageTag to use for cluster creation. If specified, overrides the .pgo.yaml setting." - // in: "path" - // type: "string" - // required: false + // - name: "PostgreSQL Scale Cluster" + // in: "body" + // schema: + // "$ref": "#/definitions/ClusterScaleRequest" // responses: // '200': // description: Output // schema: // "$ref": "#/definitions/ClusterScaleResponse" - //SCALE_CLUSTER_PERM - // This is a pain to document because it doesn't use a struct... - var ns string - vars := mux.Vars(r) - - clusterName := vars[config.LABEL_NAME] - namespace := r.URL.Query().Get(config.LABEL_NAMESPACE) - replicaCount := r.URL.Query().Get(config.LABEL_REPLICA_COUNT) - storageConfig := r.URL.Query().Get(config.LABEL_STORAGE_CONFIG) - nodeLabel := r.URL.Query().Get(config.LABEL_NODE_LABEL) - serviceType := r.URL.Query().Get(config.LABEL_SERVICE_TYPE) - clientVersion := r.URL.Query().Get(config.LABEL_VERSION) - ccpImageTag := r.URL.Query().Get(config.LABEL_CCP_IMAGE_TAG_KEY) - - log.Debugf("ScaleClusterHandler parameters name [%s] namespace [%s] replica-count [%s] "+ - "storage-config [%s] node-label [%s] service-type [%s] version [%s]"+ - "ccp-image-tag [%s]", clusterName, namespace, replicaCount, - storageConfig, nodeLabel, serviceType, clientVersion, ccpImageTag) + log.Debug("clusterservice.ScaleClusterHandler called") + // first, check that the requesting user is authorized to make this request username, err := apiserver.Authn(apiserver.SCALE_CLUSTER_PERM, w, r) if err != nil { return } - w.WriteHeader(http.StatusOK) + // decode the request parameters + request := msgs.ClusterScaleRequest{} + + if err := json.NewDecoder(r.Body).Decode(&request); err != nil { + _ = json.NewEncoder(w).Encode(msgs.ClusterScaleResponse{ + Status: msgs.Status{ + Code: msgs.Error, + Msg: err.Error(), + }, + }) + return + } + + // set some of the header...though we really should not be setting the HTTP + // Status upfront, but whatever + w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) - resp := msgs.ClusterScaleResponse{} - resp.Status = msgs.Status{Code: msgs.Ok, Msg: ""} + // determine if this is the correct client version + if request.ClientVersion != msgs.PGO_VERSION { + _ = json.NewEncoder(w).Encode(msgs.ClusterScaleResponse{ + Status: msgs.Status{ + Code: msgs.Error, + Msg: apiserver.VERSION_MISMATCH_ERROR, + }, + }) + return + } - if clientVersion != msgs.PGO_VERSION { - resp.Status = msgs.Status{Code: msgs.Error, Msg: apiserver.VERSION_MISMATCH_ERROR} - _ = json.NewEncoder(w).Encode(resp) + // ensure that the user has access to this namespace. if not, error out + if _, err := apiserver.GetNamespace(apiserver.Clientset, username, request.Namespace); err != nil { + _ = json.NewEncoder(w).Encode(msgs.ClusterScaleResponse{ + Status: msgs.Status{ + Code: msgs.Error, + Msg: err.Error(), + }, + }) return } - ns, err = apiserver.GetNamespace(apiserver.Clientset, username, namespace) - if err != nil { - resp.Status = msgs.Status{Code: msgs.Error, Msg: err.Error()} - _ = json.NewEncoder(w).Encode(resp) + // ensure that the cluster name is set in the URL, as the request parameters + // will use that as precedence + vars := mux.Vars(r) + clusterName, ok := vars[config.LABEL_NAME] + + if !ok { + _ = json.NewEncoder(w).Encode(msgs.ClusterScaleResponse{ + Status: msgs.Status{ + Code: msgs.Error, + Msg: "cluster name required in URL", + }, + }) return } - // TODO too many params need to create a struct for this - resp = ScaleCluster(clusterName, replicaCount, storageConfig, nodeLabel, - ccpImageTag, serviceType, ns, username) + request.Name = clusterName - _ = json.NewEncoder(w).Encode(resp) + response := ScaleCluster(request, username) + + _ = json.NewEncoder(w).Encode(response) } // ScaleQueryHandler ... diff --git a/internal/apiserver/routing/routes.go b/internal/apiserver/routing/routes.go index 378651e12b..eb3f69c862 100644 --- a/internal/apiserver/routing/routes.go +++ b/internal/apiserver/routing/routes.go @@ -91,7 +91,7 @@ func RegisterClusterSvcRoutes(r *mux.Router) { r.HandleFunc("/clustersdelete", clusterservice.DeleteClusterHandler).Methods("POST") r.HandleFunc("/clustersupdate", clusterservice.UpdateClusterHandler).Methods("POST") r.HandleFunc("/testclusters", clusterservice.TestClusterHandler).Methods("POST") - r.HandleFunc("/clusters/scale/{name}", clusterservice.ScaleClusterHandler) + r.HandleFunc("/clusters/scale/{name}", clusterservice.ScaleClusterHandler).Methods("POST") r.HandleFunc("/scale/{name}", clusterservice.ScaleQueryHandler).Methods("GET") r.HandleFunc("/scaledown/{name}", clusterservice.ScaleDownHandler).Methods("GET") } diff --git a/pkg/apiservermsgs/clustermsgs.go b/pkg/apiservermsgs/clustermsgs.go index 52b68909a2..b995d0ea13 100644 --- a/pkg/apiservermsgs/clustermsgs.go +++ b/pkg/apiservermsgs/clustermsgs.go @@ -533,6 +533,34 @@ type ScaleDownResponse struct { Status } +// ClusterScaleRequest superimposes on the legacy model of handling the ability +// to scale up the number of instances on a cluster +// swagger:model +type ClusterScaleRequest struct { + // CCPImageTag is the image tag to use for cluster creation. If this is not + // provided, this defaults to what the cluster is using, which is likely + // the preferred behavior at this point. + CCPImageTag string `json:"ccpImageTag"` + // ClientVersion is the version of the client that is being used + ClientVersion string `json:"clientVersion"` + // Name is the name of the cluster to scale. This is set by the value in the + // URL + Name string `json:"name"` + // Namespace is the namespace in which the queried cluster resides. + Namespace string `json:"namespace"` + // NodeLabel if provided is a node label to use. + NodeLabel string `json:"nodeLabel"` + // ReplicaCount is the number of replicas to add to the cluster. This is + // required and should be at least 1. + ReplicaCount int `json:"replicaCount"` + // ServiceType is the kind of Service to deploy with this instance. Defaults + // to the value on the cluster. + ServiceType string `json:"serviceType"` + // StorageConfig, if provided, specifies which of the storage configuration + // options should be used. Defaults to what the main cluster definition uses. + StorageConfig string `json:"storageConfig"` +} + // ClusterScaleResponse ... // swagger:model type ClusterScaleResponse struct { From 55ad25c3b43b74600122a9623a66c0f6de5ee522 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Thu, 24 Dec 2020 12:12:19 -0500 Subject: [PATCH 079/276] Add support for Pod Tolerations for pgcluster and pgreplica This patch introduces support for being able to specify Pod Tolerations both at a cluster-wide level and for individual instances specified on the pgreplica custom resource. Both of the custom resources can be modified and have their changes reconciled across all managed PostgreSQL instances. The structure for adding Tolerations matches that of the Kubernetes spec. This also introduces the "pgo create cluster --toleration" and "pgo scale cluster --toleration" flag, which can accept one or more tolerations using the format: "rule:Effect", e.g. `--toleration=ssd:NoSchedule,zone=east:NoSchedule` Issue: #2056 --- README.md | 8 +- cmd/pgo/cmd/cluster.go | 79 ++++++++++++++++++- cmd/pgo/cmd/create.go | 15 ++++ cmd/pgo/cmd/scale.go | 5 ++ docs/content/_index.md | 6 +- .../architecture/high-availability/_index.md | 52 ++++++++++++ docs/content/custom-resources/_index.md | 4 + .../reference/pgo_create_cluster.md | 5 +- .../content/pgo-client/reference/pgo_scale.md | 7 +- docs/content/tutorial/customize-cluster.md | 30 ++++++- docs/content/tutorial/high-availability.md | 14 +++- .../files/pgo-configs/cluster-deployment.json | 3 + .../apiserver/clusterservice/clusterimpl.go | 3 + .../apiserver/clusterservice/scaleimpl.go | 1 + .../controller/manager/controllermanager.go | 2 +- .../pgcluster/pgclustercontroller.go | 11 ++- .../pgreplica/pgreplicacontroller.go | 72 ++++++++++++++--- .../controller/pgtask/pgtaskcontroller.go | 2 +- internal/operator/cluster/cluster.go | 48 ++++++++++- internal/operator/cluster/clusterlogic.go | 6 ++ internal/operator/cluster/exporter.go | 2 +- internal/operator/cluster/rolling.go | 11 +-- internal/operator/clusterutilities.go | 22 ++++++ pkg/apis/crunchydata.com/v1/cluster.go | 4 + pkg/apis/crunchydata.com/v1/replica.go | 4 + .../v1/zz_generated.deepcopy.go | 14 ++++ pkg/apiservermsgs/clustermsgs.go | 6 ++ 27 files changed, 397 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index c0607435f1..784fad181e 100644 --- a/README.md +++ b/README.md @@ -61,9 +61,9 @@ Create new clusters from your existing clusters or backups with [`pgo create clu Use [pgBouncer][] for connection pooling -#### Node Affinity +#### Affinity and Tolerations -Have your PostgreSQL clusters deployed to [Kubernetes Nodes][k8s-nodes] of your preference +Have your PostgreSQL clusters deployed to [Kubernetes Nodes][k8s-nodes] of your preference with [node affinity][high-availability-node-affinity], or designate which nodes Kubernetes can schedule PostgreSQL instances to with Kubneretes [tolerations][high-availability-tolerations]. #### Scheduled Backups @@ -99,7 +99,9 @@ The Crunchy PostgreSQL Operator makes it easy to get your own PostgreSQL-as-a-Se [disaster-recovery-s3]: https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/disaster-recovery/#using-s3 [disaster-recovery-scheduling]: https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/disaster-recovery/#scheduling-backups [high-availability]: https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/high-availability/ +[high-availability-node-affinity]: https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/high-availability/#node-affinity [high-availability-sync]: https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/high-availability/#synchronous-replication-guarding-against-transactions-loss +[high-availability-tolerations]: https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/high-availability/#tolerations [monitoring]: https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/monitoring/ [multiple-cluster]: https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/high-availability/multi-cluster-kubernetes/ [pgo-create-cluster]: https://access.crunchydata.com/documentation/postgres-operator/latest/pgo-client/reference/pgo_create_cluster/ @@ -111,7 +113,7 @@ The Crunchy PostgreSQL Operator makes it easy to get your own PostgreSQL-as-a-Se [k8s-nodes]: https://kubernetes.io/docs/concepts/architecture/nodes/ [pgBackRest]: https://www.pgbackrest.org -[pgBouncer]: https://access.crunchydata.com/documentation/pgbouncer/ +[pgBouncer]: https://access.crunchydata.com/documentation/postgres-operator/latest/tutorial/pgbouncer/ [pgMonitor]: https://github.com/CrunchyData/pgmonitor diff --git a/cmd/pgo/cmd/cluster.go b/cmd/pgo/cmd/cluster.go index 4dc16a0fdc..de7b895de4 100644 --- a/cmd/pgo/cmd/cluster.go +++ b/cmd/pgo/cmd/cluster.go @@ -21,13 +21,14 @@ import ( "os" "strings" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" + v1 "k8s.io/api/core/v1" "github.com/crunchydata/postgres-operator/cmd/pgo/api" "github.com/crunchydata/postgres-operator/cmd/pgo/util" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" - log "github.com/sirupsen/logrus" ) // below are the tablespace parameters and the expected values of each @@ -333,6 +334,8 @@ func createCluster(args []string, ns string, createClusterCmd *cobra.Command) { // set any annotations r.Annotations = getClusterAnnotations(Annotations, AnnotationsPostgres, AnnotationsBackrest, AnnotationsPgBouncer) + // set any tolerations + r.Tolerations = getClusterTolerations(Tolerations) // only set SyncReplication in the request if actually provided via the CLI if createClusterCmd.Flag("sync-replication").Changed { @@ -543,6 +546,80 @@ func getTablespaces(tablespaceParams []string) []msgs.ClusterTablespaceDetail { return tablespaces } +// getClusterTolerations determines if there are any Pod tolerations to set +// and converts from the defined string form to the standard Toleration object +// +// The strings should follow the following formats: +// +// Operator - rule:Effect +// +// Exists - key:Effect +// Equals - key=value:Effect +func getClusterTolerations(tolerationList []string) []v1.Toleration { + tolerations := make([]v1.Toleration, 0) + + // if no tolerations, exit early + if len(tolerationList) == 0 { + return tolerations + } + + // begin the joys of parsing + for _, t := range tolerationList { + toleration := v1.Toleration{} + ruleEffect := strings.Split(t, ":") + + // if we don't have exactly two items, then error + if len(ruleEffect) != 2 { + fmt.Printf("invalid format for toleration: %q\n", t) + os.Exit(1) + } + + // for ease of reading + rule, effect := ruleEffect[0], v1.TaintEffect(ruleEffect[1]) + + // see if the effect is a valid effect + if !isValidTaintEffect(effect) { + fmt.Printf("invalid taint effect for toleration: %q\n", effect) + os.Exit(1) + } + + toleration.Effect = effect + + // determine if the rule is an Exists or Equals operation + keyValue := strings.Split(rule, "=") + + if len(keyValue) < 1 || len(keyValue) > 2 { + fmt.Printf("invalid rule for toleration: %q\n", rule) + os.Exit(1) + } + + // no matter what we have a key + toleration.Key = keyValue[0] + + // the following determine the operation to use for the toleration and if + // we should assign a value + if len(keyValue) == 1 { + toleration.Operator = v1.TolerationOpExists + } else { + toleration.Operator = v1.TolerationOpEqual + toleration.Value = keyValue[1] + } + + // and append to the list of tolerations + tolerations = append(tolerations, toleration) + } + + return tolerations +} + +// isValidTaintEffect returns true if the effect passed in is a valid +// TaintEffect, otherwise false +func isValidTaintEffect(taintEffect v1.TaintEffect) bool { + return (taintEffect == v1.TaintEffectNoSchedule || + taintEffect == v1.TaintEffectPreferNoSchedule || + taintEffect == v1.TaintEffectNoExecute) +} + // isTablespaceParam returns true if the parameter in question is acceptable for // using with a tablespace. func isTablespaceParam(param string) bool { diff --git a/cmd/pgo/cmd/create.go b/cmd/pgo/cmd/create.go index 2e6bf8c41e..aff3f53dca 100644 --- a/cmd/pgo/cmd/create.go +++ b/cmd/pgo/cmd/create.go @@ -142,6 +142,17 @@ var ( CASecret string ) +// Tolerations is a collection of Pod tolerations that can be applied, which +// use the following format for the different operations +// +// Exists - key:Effect +// Equals - key=value:Effect +// +// Example: +// +// zone=east:NoSchedule,highspeed:NoSchedule +var Tolerations []string + var CreateCmd = &cobra.Command{ Use: "create", Short: "Create a Postgres Operator resource", @@ -476,6 +487,10 @@ func init() { "Enables synchronous replication for the cluster.") createClusterCmd.Flags().BoolVar(&TLSOnly, "tls-only", false, "If true, forces all PostgreSQL connections to be over TLS. "+ "Must also set \"server-tls-secret\" and \"server-ca-secret\"") + createClusterCmd.Flags().StringSliceVar(&Tolerations, "toleration", []string{}, + "Set Pod tolerations for each PostgreSQL instance in a cluster.\n"+ + "The general format is \"key=value:Effect\"\n"+ + "For example, to add an Exists and an Equals toleration: \"--toleration=ssd:NoSchedule,zone=east:NoSchedule\"") createClusterCmd.Flags().BoolVarP(&Standby, "standby", "", false, "Creates a standby cluster "+ "that replicates from a pgBackRest repository in AWS S3.") createClusterCmd.Flags().StringSliceVar(&Tablespaces, "tablespace", []string{}, diff --git a/cmd/pgo/cmd/scale.go b/cmd/pgo/cmd/scale.go index d74a38f035..6352e91cd3 100644 --- a/cmd/pgo/cmd/scale.go +++ b/cmd/pgo/cmd/scale.go @@ -62,6 +62,10 @@ func init() { scaleCmd.Flags().BoolVar(&NoPrompt, "no-prompt", false, "No command line confirmation.") scaleCmd.Flags().IntVarP(&ReplicaCount, "replica-count", "", 1, "The replica count to apply to the clusters.") scaleCmd.Flags().StringVarP(&StorageConfig, "storage-config", "", "", "The name of a Storage config in pgo.yaml to use for the replica storage.") + scaleCmd.Flags().StringSliceVar(&Tolerations, "toleration", []string{}, + "Set Pod tolerations for each PostgreSQL instance in a cluster.\n"+ + "The general format is \"key=value:Effect\"\n"+ + "For example, to add an Exists and an Equals toleration: \"--toleration=ssd:NoSchedule,zone=east:NoSchedule\"") } func scaleCluster(args []string, ns string) { @@ -74,6 +78,7 @@ func scaleCluster(args []string, ns string) { ReplicaCount: ReplicaCount, ServiceType: ServiceType, StorageConfig: StorageConfig, + Tolerations: getClusterTolerations(Tolerations), } response, err := api.ScaleCluster(httpclient, &SessionCredentials, request) diff --git a/docs/content/_index.md b/docs/content/_index.md index b879f3db2a..96f6807560 100644 --- a/docs/content/_index.md +++ b/docs/content/_index.md @@ -56,11 +56,11 @@ Create new clusters from your existing clusters or backups with [`pgo create clu #### Connection Pooling - Use [pgBouncer](https://access.crunchydata.com/documentation/pgbouncer/) for connection pooling + Use [pgBouncer]({{< relref "tutorial/pgbouncer.md" >}}) for connection pooling. -#### Node Affinity +#### Affinity and Tolerations -Have your PostgreSQL clusters deployed to [Kubernetes Nodes](https://kubernetes.io/docs/concepts/architecture/nodes/) of your preference +Have your PostgreSQL clusters deployed to [Kubernetes Nodes](https://kubernetes.io/docs/concepts/architecture/nodes/) of your preference with [node affinity]({{< relref "architecture/high-availability/_index.md">}}#node-affinity), or designate which nodes Kubernetes can schedule PostgreSQL instances to with [tolerations]({{< relref "architecture/high-availability/_index.md">}}#tolerations). #### Scheduled Backups diff --git a/docs/content/architecture/high-availability/_index.md b/docs/content/architecture/high-availability/_index.md index b3dc97f290..3a5d79c806 100644 --- a/docs/content/architecture/high-availability/_index.md +++ b/docs/content/architecture/high-availability/_index.md @@ -277,6 +277,57 @@ is described in the Pod Anti-Affinity section above), so if a Pod cannot be scheduled to a particular Node matching the label, it will be scheduled to a different Node. +## Tolerations + +Kubernetes [Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) +can help with the scheduling of Pods to appropriate nodes. There are many +reasons that a Kubernetes administrator may want to use tolerations, such as +restricting the types of Pods that can be assigned to particular Nodes. +Reasoning and strategy for using taints and tolerations is outside the scope of +this documentation. + +The PostgreSQL Operator supports the setting of tolerations across all +PostgreSQL instances in a cluster, as well as for each particular PostgreSQL +instance within a cluster. Both the [`pgo create cluster`]({{< relref "pgo-client/reference/pgo_create_cluster.md">}}) +and [`pgo scale`]({{< relref "pgo-client/reference/pgo_scale.md">}}) commands +support the `--toleration` flag, which allows for one or more tolerations to be +added to a PostgreSQL cluster. Values accepted by the `--toleration` use the +following format: + +``` +rule:Effect +``` + +where a `rule` can represent existence (e.g. `key`) or equality (`key=value`) +and `Effect` is one of `NoSchedule`, `PreferNoSchedule`, or `NoExecute`. For +more information on how tolerations work, please refer to the +[Kubernetes documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/). + +For example, to add two tolerations to a new PostgreSQL cluster, one that is an +existence toleration for a key of `ssd` and the other that is an equality +toleration for a key/value pair of `zone`/`east`, you can run the following +command: + +``` +pgo create cluster hippo \ + --toleration=ssd:NoSchedule \ + --toleration=zone=east:NoSchedule +``` + +For another example, to assign equality toleration for a key/value pair of +`zone`/`west` to a new instance in the `hippo` cluster, you can run the +following command: + +``` +pgo scale hippo --toleration=zone=west:NoSchedule +``` + +Tolerations can be updated on an existing cluster. To do so, you will need to +modify the `pgclusters.crunchydata.com` and `pgreplicas.crunchydata.com` custom +resources directly, e.g. via the `kubectl edit` command. Once the updates are +applied, the PostgreSQL Operator will roll out the changes to the appropriate +instances. + ## Rolling Updates During the lifecycle of a PostgreSQL cluster, there are certain events that may @@ -332,3 +383,4 @@ modification to the custom resource: - Custom annotation changes - Enabling/disabling the monitoring sidecar on a PostgreSQL cluster (`--metrics`) - Tablespace additions +- Toleration modifications diff --git a/docs/content/custom-resources/_index.md b/docs/content/custom-resources/_index.md index 6a578bc242..b323911e18 100644 --- a/docs/content/custom-resources/_index.md +++ b/docs/content/custom-resources/_index.md @@ -201,6 +201,7 @@ spec: replicationTLSSecret: "" tlsSecret: "" tlsOnly: false + tolerations: [] user: hippo userlabels: crunchy-postgres-exporter: "false" @@ -392,6 +393,7 @@ spec: replicationTLSSecret: "" tlsSecret: "" tlsOnly: false + tolerations: [] user: hippo userlabels: backrest-storage-type: "s3" @@ -713,6 +715,7 @@ make changes, as described below. | TablespaceMounts | `create`,`update` | Lists any tablespaces that are attached to the PostgreSQL cluster. Tablespaces can be added at a later time by updating the `TablespaceMounts` entry, but they cannot be removed. Stores a map of information, with the key being the name of the tablespace, and the value being a Storage Specification, defined below. | | TLS | `create` | Defines the attributes for enabling TLS for a PostgreSQL cluster. See TLS Specification below. | | TLSOnly | `create` | If set to true, requires client connections to use only TLS to connect to the PostgreSQL database. | +| Tolerations | `create`,`update` | Any array of Kubernetes [Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/). Please refer to the [Kubernetes documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) for how to set this field. | | Standby | `create`, `update` | If set to true, indicates that the PostgreSQL cluster is a "standby" cluster, i.e. is in read-only mode entirely. Please see [Kubernetes Multi-Cluster Deployments]({{< relref "/architecture/high-availability/multi-cluster-kubernetes.md" >}}) for more information. | | Shutdown | `create`, `update` | If set to true, indicates that a PostgreSQL cluster should shutdown. If set to false, indicates that a PostgreSQL cluster should be up and running. | @@ -826,3 +829,4 @@ cluster. All of the attributes only affect the replica when it is created. | Namespace | `create` | The Kubernetes Namespace that the PostgreSQL cluster is deployed in. | | ReplicaStorage | `create` | A specification that gives information about the storage attributes for any replicas in the PostgreSQL cluster. For details, please see the `Storage Specification` section in the `pgclusters.crunchydata.com` description. This will likely be changed in the future based on the nature of the high-availability system, but presently it is still required that you set it. It is recommended you use similar settings to that of `PrimaryStorage`. | | UserLabels | `create` | A set of key-value string pairs that are used as a sort of "catch-all" for things that really should be modeled in the CRD. These values do get copied to the actually CR labels. If you want to set up metrics collection, you would specify `"crunchy-postgres-exporter": "true"` here. This also allows for node selector pinning using `NodeLabelKey` and `NodeLabelValue`. However, this structure does need to be set, so just follow whatever is in the example. | +| Tolerations | `create`,`update` | Any array of Kubernetes [Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/). Please refer to the [Kubernetes documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) for how to set this field. | diff --git a/docs/content/pgo-client/reference/pgo_create_cluster.md b/docs/content/pgo-client/reference/pgo_create_cluster.md index 7a8661845d..efc7edc738 100644 --- a/docs/content/pgo-client/reference/pgo_create_cluster.md +++ b/docs/content/pgo-client/reference/pgo_create_cluster.md @@ -109,6 +109,9 @@ pgo create cluster [flags] --tablespace=name=ts1:storageconfig=nfsstorage:pvcsize=10Gi --tls-only If true, forces all PostgreSQL connections to be over TLS. Must also set "server-tls-secret" and "server-ca-secret" + --toleration strings Set Pod tolerations for each PostgreSQL instance in a cluster. + The general format is "key=value:Effect" + For example, to add an Exists and an Equals toleration: "--toleration=ssd:NoSchedule,zone=east:NoSchedule" -u, --username string The username to use for creating the PostgreSQL user with standard permissions. Defaults to the value in the PostgreSQL Operator configuration. --wal-storage-config string The name of a storage configuration in pgo.yaml to use for PostgreSQL's write-ahead log (WAL). --wal-storage-size string The size of the capacity for WAL storage, which overrides any value in the storage configuration. Follows the Kubernetes quantity format. @@ -131,4 +134,4 @@ pgo create cluster [flags] * [pgo create](/pgo-client/reference/pgo_create/) - Create a Postgres Operator resource -###### Auto generated by spf13/cobra on 22-Nov-2020 +###### Auto generated by spf13/cobra on 24-Dec-2020 diff --git a/docs/content/pgo-client/reference/pgo_scale.md b/docs/content/pgo-client/reference/pgo_scale.md index 684d506cc8..146645d08c 100644 --- a/docs/content/pgo-client/reference/pgo_scale.md +++ b/docs/content/pgo-client/reference/pgo_scale.md @@ -25,12 +25,15 @@ pgo scale [flags] --replica-count int The replica count to apply to the clusters. (default 1) --service-type string The service type to use in the replica Service. If not set, the default in pgo.yaml will be used. --storage-config string The name of a Storage config in pgo.yaml to use for the replica storage. + --toleration strings Set Pod tolerations for each PostgreSQL instance in a cluster. + The general format is "key=value:Effect" + For example, to add an Exists and an Equals toleration: "--toleration=ssd:NoSchedule,zone=east:NoSchedule" ``` ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -44,4 +47,4 @@ pgo scale [flags] * [pgo](/pgo-client/reference/pgo/) - The pgo command line interface. -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 25-Dec-2020 diff --git a/docs/content/tutorial/customize-cluster.md b/docs/content/tutorial/customize-cluster.md index 3b30d0f0f2..3cd1f6d374 100644 --- a/docs/content/tutorial/customize-cluster.md +++ b/docs/content/tutorial/customize-cluster.md @@ -7,7 +7,7 @@ weight: 130 The PostgreSQL Operator makes it very easy and quick to [create a cluster]({{< relref "tutorial/create-cluster.md" >}}), but there are possibly more customizations you want to make to your cluster. These include: - Resource allocations (e.g. Memory, CPU, PVC size) -- Sidecars (e.g. [Monitoring]({{< relref "architecture/monitoring.md" >}}), pgBouncer, [pgAdmin 4]({{< relref "architecture/pgadmin4.md" >}})) +- Sidecars (e.g. [Monitoring]({{< relref "architecture/monitoring.md" >}}), [pgBouncer]({{< relref "tutorial/pgbouncer.md" >}}), [pgAdmin 4]({{< relref "architecture/pgadmin4.md" >}})) - High Availability (e.g. adding replicas) - Specifying specific PostgreSQL images (e.g. one with PostGIS) - Specifying a [Pod anti-affinity and Node affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/) @@ -136,6 +136,30 @@ pgo create cluster hippo --replica-count=1 You can scale up and down your PostgreSQL cluster with the [`pgo scale`]({{< relref "pgo-client/reference/pgo_scale.md" >}}) and [`pgo scaledown`]({{< relref "pgo-client/reference/pgo_scaledown.md" >}}) commands. +## Set Tolerations for a PostgreSQL Cluster + +[Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) help with the scheduling of Pods to appropriate nodes. There are many reasons that a Kubernetes administrator may want to use tolerations, such as restricting the types of Pods that can be assigned to particular nodes. + +The PostgreSQL Operator supports adding tolerations to PostgreSQL instances using the `--toleration` flag. The format for adding a toleration is as such: + +``` +rule:Effect +``` + +where a `rule` can represent existence (e.g. `key`) or equality (`key=value`) and `Effect` is one of `NoSchedule`, `PreferNoSchedule`, or `NoExecute`. For more information on how tolerations work, please refer to the [Kubernetes documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/). + +You can assign multiple tolerations to a PostgreSQL cluster. + +For example, to add two tolerations to a new PostgreSQL cluster, one that is an existence toleration for a key of `ssd` and the other that is an equality toleration for a key/value pair of `zone`/`east`, you can run the following command: + +``` +pgo create cluster hippo \ + --toleration=ssd:NoSchedule \ + --toleration=zone=east:NoSchedule +``` + +You can also add or edit tolerations directly on the `pgclusters.crunchydata.com` custom resource and the PostgreSQL Operator will roll out the changes to the appropriate instances. + ## Customize PostgreSQL Configuration PostgreSQL provides a lot of different knobs that can be used to fine tune the [configuration](https://www.postgresql.org/docs/current/runtime-config.html) for your workload. While you can [customize your PostgreSQL configuration]({{< relref "advanced/custom-configuration.md" >}}) after your cluster has been deployed, you may also want to load in your custom configuration during initialization. @@ -213,6 +237,10 @@ has successfully started. - The password for the `ccp_monitoring` user has changed. In this case you will need to update the Secret with the monitoring credentials. +### PostgreSQL Pod Not Scheduled to Nodes Matching Tolerations + +While Kubernetes [Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) allow for Pods to be scheduled to Nodes based on their taints, this does not mean that the Pod _will_ be assigned to those nodes. To provide Kubernetes scheduling guidance on where a Pod should be assigned, you must also use [Node Affinity]({{< relref "architecture/high-availability/_index.md" >}}#node-affinity). + ## Next Steps As mentioned at the beginning, there are a lot more customizations that you can make to your PostgreSQL cluster, and we will cover those as the tutorial progresses! This section was to get you familiar with some of the most common customizations, and to explore how many options `pgo create cluster` has! diff --git a/docs/content/tutorial/high-availability.md b/docs/content/tutorial/high-availability.md index 3a528ab456..b85a8469cc 100644 --- a/docs/content/tutorial/high-availability.md +++ b/docs/content/tutorial/high-availability.md @@ -94,7 +94,19 @@ Please understand the tradeoffs of synchronous replication before using it. ## Pod Anti-Affinity and Node Affinity -To leran how to use pod anti-affinity and node affinity, please refer to the [high availability architecture documentation]({{< relref "architecture/high-availability/_index.md" >}}) +To learn how to use pod anti-affinity and node affinity, please refer to the [high availability architecture documentation]({{< relref "architecture/high-availability/_index.md" >}}). + +## Tolerations + +If you want to have a PostgreSQL instance use specific Kubernetes [tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/), you can use the `--toleration` flag on [`pgo scale`]({{< relref "pgo-client/reference/pgo_scale.md">}}). Any tolerations added to the new PostgreSQL instance fully replace any tolerations available to the entire cluster. + +For example, to assign equality toleration for a key/value pair of `zone`/`west`, you can run the following command: + +``` +pgo scale hippo --toleration=zone=west:NoSchedule +``` + +For more information on the PostgreSQL Operator and tolerations, please review the [high availability architecture documentation]({{< relref "architecture/high-availability/_index.md" >}}). ## Troubleshooting diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json index 33c02937c4..f5fb452849 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json @@ -34,6 +34,9 @@ "spec": { "securityContext": {{.SecurityContext}}, "serviceAccountName": "pgo-pg", + {{ if .Tolerations }} + "tolerations": {{ .Tolerations }}, + {{ end }} "containers": [ { "name": "database", diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index 3745d1d220..21fc5f24ba 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -1490,6 +1490,9 @@ func getClusterParams(request *msgs.CreateClusterRequest, name string, userLabel setClusterAnnotationGroup(spec.Annotations.Backrest, request.Annotations.Backrest) setClusterAnnotationGroup(spec.Annotations.PgBouncer, request.Annotations.PgBouncer) + // set any tolerations + spec.Tolerations = request.Tolerations + labels := make(map[string]string) labels[config.LABEL_NAME] = name if !request.AutofailFlag || apiserver.Pgo.Cluster.DisableAutofail { diff --git a/internal/apiserver/clusterservice/scaleimpl.go b/internal/apiserver/clusterservice/scaleimpl.go index b8dadef637..7713f33eb0 100644 --- a/internal/apiserver/clusterservice/scaleimpl.go +++ b/internal/apiserver/clusterservice/scaleimpl.go @@ -119,6 +119,7 @@ func ScaleCluster(request msgs.ClusterScaleRequest, pgouser string) msgs.Cluster labels[config.LABEL_PG_CLUSTER] = cluster.Spec.Name spec.ClusterName = cluster.Spec.Name + spec.Tolerations = request.Tolerations labels[config.LABEL_PGOUSER] = pgouser labels[config.LABEL_PG_CLUSTER_IDENTIFIER] = cluster.ObjectMeta.Labels[config.LABEL_PG_CLUSTER_IDENTIFIER] diff --git a/internal/controller/manager/controllermanager.go b/internal/controller/manager/controllermanager.go index 4a6ac9dc2c..165677b2f2 100644 --- a/internal/controller/manager/controllermanager.go +++ b/internal/controller/manager/controllermanager.go @@ -256,7 +256,7 @@ func (c *ControllerManager) addControllerGroup(namespace string) error { } pgReplicacontroller := &pgreplica.Controller{ - Clientset: client, + Client: client, Queue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()), Informer: pgoInformerFactory.Crunchydata().V1().Pgreplicas(), PgreplicaWorkerCount: *c.pgoConfig.Pgo.PGReplicaWorkerCount, diff --git a/internal/controller/pgcluster/pgclustercontroller.go b/internal/controller/pgcluster/pgclustercontroller.go index 5c745fec7c..02789dce9b 100644 --- a/internal/controller/pgcluster/pgclustercontroller.go +++ b/internal/controller/pgcluster/pgclustercontroller.go @@ -178,7 +178,7 @@ func (c *Controller) onUpdate(oldObj, newObj interface{}) { newcluster := newObj.(*crv1.Pgcluster) // initialize a slice that may contain functions that need to be executed // as part of a rolling update - rollingUpdateFuncs := [](func(*crv1.Pgcluster, *appsv1.Deployment) error){} + rollingUpdateFuncs := [](func(kubeapi.Interface, *crv1.Pgcluster, *appsv1.Deployment) error){} log.Debugf("pgcluster onUpdate for cluster %s (namespace %s)", newcluster.ObjectMeta.Namespace, newcluster.ObjectMeta.Name) @@ -305,6 +305,11 @@ func (c *Controller) onUpdate(oldObj, newObj interface{}) { } } + // check to see if any tolerations have been modified + if !reflect.DeepEqual(oldcluster.Spec.Tolerations, newcluster.Spec.Tolerations) { + rollingUpdateFuncs = append(rollingUpdateFuncs, clusteroperator.UpdateTolerations) + } + // if there is no need to perform a rolling update, exit here if len(rollingUpdateFuncs) == 0 { return @@ -313,9 +318,9 @@ func (c *Controller) onUpdate(oldObj, newObj interface{}) { // otherwise, create an anonymous function that executes each of the rolling // update functions as part of the rolling update if err := clusteroperator.RollingUpdate(c.Client, c.Client.Config, newcluster, - func(cluster *crv1.Pgcluster, deployment *appsv1.Deployment) error { + func(clientset kubeapi.Interface, cluster *crv1.Pgcluster, deployment *appsv1.Deployment) error { for _, fn := range rollingUpdateFuncs { - if err := fn(cluster, deployment); err != nil { + if err := fn(clientset, cluster, deployment); err != nil { return err } } diff --git a/internal/controller/pgreplica/pgreplicacontroller.go b/internal/controller/pgreplica/pgreplicacontroller.go index 91325b7066..79f8538100 100644 --- a/internal/controller/pgreplica/pgreplicacontroller.go +++ b/internal/controller/pgreplica/pgreplicacontroller.go @@ -18,15 +18,20 @@ limitations under the License. import ( "context" "encoding/json" + "reflect" "strings" "github.com/crunchydata/postgres-operator/internal/config" "github.com/crunchydata/postgres-operator/internal/kubeapi" clusteroperator "github.com/crunchydata/postgres-operator/internal/operator/cluster" + "github.com/crunchydata/postgres-operator/internal/util" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" informers "github.com/crunchydata/postgres-operator/pkg/generated/informers/externalversions/crunchydata.com/v1" + log "github.com/sirupsen/logrus" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" @@ -34,7 +39,7 @@ import ( // Controller holds the connections for the controller type Controller struct { - Clientset kubeapi.Interface + Client *kubeapi.Client Queue workqueue.RateLimitingInterface Informer informers.PgreplicaInformer PgreplicaWorkerCount int @@ -84,7 +89,7 @@ func (c *Controller) processNextItem() bool { // in this case, the de-dupe logic is to test whether a replica // deployment exists already , if so, then we don't create another // backup job - _, err := c.Clientset. + _, err := c.Client. AppsV1().Deployments(keyNamespace). Get(ctx, keyResourceName, metav1.GetOptions{}) @@ -97,7 +102,7 @@ func (c *Controller) processNextItem() bool { // handle the case of when a pgreplica is added which is // scaling up a cluster - replica, err := c.Clientset.CrunchydataV1().Pgreplicas(keyNamespace).Get(ctx, keyResourceName, metav1.GetOptions{}) + replica, err := c.Client.CrunchydataV1().Pgreplicas(keyNamespace).Get(ctx, keyResourceName, metav1.GetOptions{}) if err != nil { log.Error(err) c.Queue.Forget(key) // NB(cbandy): This should probably be a retry. @@ -105,7 +110,7 @@ func (c *Controller) processNextItem() bool { } // get the pgcluster resource for the cluster the replica is a part of - cluster, err := c.Clientset.CrunchydataV1().Pgclusters(keyNamespace).Get(ctx, replica.Spec.ClusterName, metav1.GetOptions{}) + cluster, err := c.Client.CrunchydataV1().Pgclusters(keyNamespace).Get(ctx, replica.Spec.ClusterName, metav1.GetOptions{}) if err != nil { log.Error(err) c.Queue.Forget(key) // NB(cbandy): This should probably be a retry. @@ -114,7 +119,7 @@ func (c *Controller) processNextItem() bool { // only process pgreplica if cluster has been initialized if cluster.Status.State == crv1.PgclusterStateInitialized { - clusteroperator.ScaleBase(c.Clientset, replica, replica.ObjectMeta.Namespace) + clusteroperator.ScaleBase(c.Client, replica, replica.ObjectMeta.Namespace) patch, err := json.Marshal(map[string]interface{}{ "status": crv1.PgreplicaStatus{ @@ -123,7 +128,7 @@ func (c *Controller) processNextItem() bool { }, }) if err == nil { - _, err = c.Clientset.CrunchydataV1().Pgreplicas(replica.Namespace). + _, err = c.Client.CrunchydataV1().Pgreplicas(replica.Namespace). Patch(ctx, replica.Name, types.MergePatchType, patch, metav1.PatchOptions{}) } if err != nil { @@ -137,7 +142,7 @@ func (c *Controller) processNextItem() bool { }, }) if err == nil { - _, err = c.Clientset.CrunchydataV1().Pgreplicas(replica.Namespace). + _, err = c.Client.CrunchydataV1().Pgreplicas(replica.Namespace). Patch(ctx, replica.Name, types.MergePatchType, patch, metav1.PatchOptions{}) } if err != nil { @@ -172,13 +177,14 @@ func (c *Controller) onAdd(obj interface{}) { func (c *Controller) onUpdate(oldObj, newObj interface{}) { ctx := context.TODO() + oldPgreplica := oldObj.(*crv1.Pgreplica) newPgreplica := newObj.(*crv1.Pgreplica) log.Debugf("[pgreplica Controller] onUpdate ns=%s %s", newPgreplica.ObjectMeta.Namespace, newPgreplica.ObjectMeta.SelfLink) // get the pgcluster resource for the cluster the replica is a part of - cluster, err := c.Clientset. + cluster, err := c.Client. CrunchydataV1().Pgclusters(newPgreplica.Namespace). Get(ctx, newPgreplica.Spec.ClusterName, metav1.GetOptions{}) if err != nil { @@ -188,7 +194,7 @@ func (c *Controller) onUpdate(oldObj, newObj interface{}) { // only process pgreplica if cluster has been initialized if cluster.Status.State == crv1.PgclusterStateInitialized && newPgreplica.Spec.Status != "complete" { - clusteroperator.ScaleBase(c.Clientset, newPgreplica, + clusteroperator.ScaleBase(c.Client, newPgreplica, newPgreplica.ObjectMeta.Namespace) patch, err := json.Marshal(map[string]interface{}{ @@ -198,13 +204,55 @@ func (c *Controller) onUpdate(oldObj, newObj interface{}) { }, }) if err == nil { - _, err = c.Clientset.CrunchydataV1().Pgreplicas(newPgreplica.Namespace). + _, err = c.Client.CrunchydataV1().Pgreplicas(newPgreplica.Namespace). Patch(ctx, newPgreplica.Name, types.MergePatchType, patch, metav1.PatchOptions{}) } if err != nil { log.Errorf("ERROR updating pgreplica status: %s", err.Error()) } } + + // if the tolerations array changed, updated the tolerations on the instance + if !reflect.DeepEqual(oldPgreplica.Spec.Tolerations, newPgreplica.Spec.Tolerations) { + // get the Deployment object associated with this instance + deployment, err := c.Client.AppsV1().Deployments(newPgreplica.Namespace).Get(ctx, + newPgreplica.Name, metav1.GetOptions{}) + + if err != nil { + log.Errorf("could not find instance for pgreplica: %q", err.Error()) + return + } + + // determine the current Pod -- this is required to stop the instance + pods, err := c.Client.CoreV1().Pods(deployment.Namespace).List(ctx, metav1.ListOptions{ + FieldSelector: fields.OneTermEqualSelector("status.phase", string(v1.PodRunning)).String(), + LabelSelector: fields.OneTermEqualSelector(config.LABEL_DEPLOYMENT_NAME, deployment.Name).String(), + }) + + // Even if there are errors with the Pods, we will continue on updating the + // Deployment + if err != nil { + log.Warn(err) + } else if len(pods.Items) == 0 { + log.Infof("not shutting down PostgreSQL instance [%s] as the Pod cannot be found", deployment.Name) + } else { + // get the first pod off the items list + pod := pods.Items[0] + + // we want to stop PostgreSQL on this instance to ensure all transactions + // are safely flushed before we restart + if err := util.StopPostgreSQLInstance(c.Client, c.Client.Config, &pod, deployment.Name); err != nil { + log.Warn(err) + } + } + + // apply the tolerations and update the Deployment + deployment.Spec.Template.Spec.Tolerations = newPgreplica.Spec.Tolerations + + if _, err := c.Client.AppsV1().Deployments(deployment.Namespace).Update(ctx, deployment, metav1.UpdateOptions{}); err != nil { + log.Errorf("could not update deployment for pgreplica update: %q", err.Error()) + } + } } // onDelete is called when a pgreplica is deleted @@ -215,7 +263,7 @@ func (c *Controller) onDelete(obj interface{}) { // make sure we are not removing a replica deployment // that is now the primary after a failover - dep, err := c.Clientset. + dep, err := c.Client. AppsV1().Deployments(replica.ObjectMeta.Namespace). Get(ctx, replica.Spec.Name, metav1.GetOptions{}) if err == nil { @@ -224,7 +272,7 @@ func (c *Controller) onDelete(obj interface{}) { // we will not scale down the deployment log.Debugf("[pgreplica Controller] OnDelete not scaling down the replica since it is acting as a primary") } else { - clusteroperator.ScaleDownBase(c.Clientset, replica, replica.ObjectMeta.Namespace) + clusteroperator.ScaleDownBase(c.Client, replica, replica.ObjectMeta.Namespace) } } } diff --git a/internal/controller/pgtask/pgtaskcontroller.go b/internal/controller/pgtask/pgtaskcontroller.go index 788de4606a..4e3f041a99 100644 --- a/internal/controller/pgtask/pgtaskcontroller.go +++ b/internal/controller/pgtask/pgtaskcontroller.go @@ -137,7 +137,7 @@ func (c *Controller) processNextItem() bool { if cluster, err := c.Client.CrunchydataV1().Pgclusters(tmpTask.Namespace). Get(ctx, clusterName, metav1.GetOptions{}); err == nil { if err := clusteroperator.RollingUpdate(c.Client, c.Client.Config, cluster, - func(*crv1.Pgcluster, *appsv1.Deployment) error { return nil }); err != nil { + func(kubeapi.Interface, *crv1.Pgcluster, *appsv1.Deployment) error { return nil }); err != nil { log.Errorf("rolling update failed: %q", err.Error()) } } else { diff --git a/internal/operator/cluster/cluster.go b/internal/operator/cluster/cluster.go index 464e1bd28e..e01b3a837b 100644 --- a/internal/operator/cluster/cluster.go +++ b/internal/operator/cluster/cluster.go @@ -39,6 +39,7 @@ import ( v1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" @@ -470,7 +471,7 @@ func ScaleDownBase(clientset kubeapi.Interface, replica *crv1.Pgreplica, namespa // UpdateAnnotations updates the annotations in the "template" portion of a // PostgreSQL deployment -func UpdateAnnotations(cluster *crv1.Pgcluster, deployment *apps_v1.Deployment) error { +func UpdateAnnotations(clientset kubeapi.Interface, cluster *crv1.Pgcluster, deployment *apps_v1.Deployment) error { log.Debugf("update annotations on [%s]", deployment.Name) annotations := map[string]string{} @@ -494,7 +495,7 @@ func UpdateAnnotations(cluster *crv1.Pgcluster, deployment *apps_v1.Deployment) // UpdateResources updates the PostgreSQL instance Deployments to reflect the // update resources (i.e. CPU, memory) -func UpdateResources(cluster *crv1.Pgcluster, deployment *apps_v1.Deployment) error { +func UpdateResources(clientset kubeapi.Interface, cluster *crv1.Pgcluster, deployment *apps_v1.Deployment) error { // iterate through each PostgreSQL instance deployment and update the // resource values for the database or exporter containers for index, container := range deployment.Spec.Template.Spec.Containers { @@ -534,7 +535,7 @@ func UpdateResources(cluster *crv1.Pgcluster, deployment *apps_v1.Deployment) er // UpdateTablespaces updates the PostgreSQL instance Deployments to update // what tablespaces are mounted. -func UpdateTablespaces(cluster *crv1.Pgcluster, deployment *apps_v1.Deployment) error { +func UpdateTablespaces(clientset kubeapi.Interface, cluster *crv1.Pgcluster, deployment *apps_v1.Deployment) error { // update the volume portion of the Deployment spec to reflect all of the // available tablespaces for tablespaceName, storageSpec := range cluster.Spec.TablespaceMounts { @@ -610,6 +611,42 @@ func UpdateTablespaces(cluster *crv1.Pgcluster, deployment *apps_v1.Deployment) return nil } +// UpdateTolerations updates the Toleration definition for a Deployment. +// However, we have to check if the Deployment is based on a pgreplica Spec -- +// if it is, we need to determine if there are any instance specific tolerations +// defined on that +func UpdateTolerations(clientset kubeapi.Interface, cluster *crv1.Pgcluster, deployment *apps_v1.Deployment) error { + ctx := context.TODO() + + // determine if this instance is based on the pgcluster or a pgreplica. if + // it is based on the pgcluster, we can apply the tolerations and exit early + if deployment.Name == cluster.Name { + deployment.Spec.Template.Spec.Tolerations = cluster.Spec.Tolerations + return nil + } + + // ok, so this is based on a pgreplica. Let's try to find it. + instance, err := clientset.CrunchydataV1().Pgreplicas(cluster.Namespace).Get(ctx, deployment.Name, metav1.GetOptions{}) + + // if we error, log it and return, as this error will interrupt a rolling update + if err != nil { + log.Error(err) + return err + } + + // if the instance does have specific tolerations, exit here as we do not + // want to override them + if len(instance.Spec.Tolerations) != 0 { + return nil + } + + // otherwise, the tolerations set on the cluster instance are available to + // all instances, so set the value and return + deployment.Spec.Template.Spec.Tolerations = cluster.Spec.Tolerations + + return nil +} + // annotateBackrestSecret annotates the pgBackRest repository secret with relevant cluster // configuration as needed to support bootstrapping from the repository after the cluster // has been deleted @@ -726,7 +763,10 @@ func stopPostgreSQLInstance(clientset kubernetes.Interface, restConfig *rest.Con // First, attempt to get the PostgreSQL instance Pod attachd to this // particular deployment selector := fmt.Sprintf("%s=%s", config.LABEL_DEPLOYMENT_NAME, deployment.Name) - pods, err := clientset.CoreV1().Pods(deployment.Namespace).List(ctx, metav1.ListOptions{LabelSelector: selector}) + pods, err := clientset.CoreV1().Pods(deployment.Namespace).List(ctx, metav1.ListOptions{ + FieldSelector: fields.OneTermEqualSelector("status.phase", string(v1.PodRunning)).String(), + LabelSelector: selector, + }) // if there is a bona fide error, return. // However, if no Pods are found, issue a warning, but do not return an error diff --git a/internal/operator/cluster/clusterlogic.go b/internal/operator/cluster/clusterlogic.go index 2a779614d2..2eca688f6a 100644 --- a/internal/operator/cluster/clusterlogic.go +++ b/internal/operator/cluster/clusterlogic.go @@ -340,6 +340,7 @@ func getClusterDeploymentFields(clientset kubernetes.Interface, ReplicationTLSSecret: cl.Spec.TLS.ReplicationTLSSecret, CASecret: cl.Spec.TLS.CASecret, Standby: cl.Spec.Standby, + Tolerations: operator.GetTolerations(cl.Spec.Tolerations), } return deploymentFields @@ -494,6 +495,11 @@ func scaleReplicaCreateDeployment(clientset kubernetes.Interface, TLSSecret: cluster.Spec.TLS.TLSSecret, ReplicationTLSSecret: cluster.Spec.TLS.ReplicationTLSSecret, CASecret: cluster.Spec.TLS.CASecret, + // Give precedence to the tolerations defined on the replica spec, otherwise + // take any tolerations defined on the cluster spec + Tolerations: util.GetValueOrDefault( + operator.GetTolerations(replica.Spec.Tolerations), + operator.GetTolerations(cluster.Spec.Tolerations)), } switch replica.Spec.ReplicaStorage.StorageType { diff --git a/internal/operator/cluster/exporter.go b/internal/operator/cluster/exporter.go index 1da55df006..e02fdf6bfe 100644 --- a/internal/operator/cluster/exporter.go +++ b/internal/operator/cluster/exporter.go @@ -265,7 +265,7 @@ func RotateExporterPassword(clientset kubernetes.Interface, restconfig *rest.Con // UpdateExporterSidecar either adds or emoves the metrics sidcar from the // cluster. This is meant to be used as a rolling update callback function -func UpdateExporterSidecar(cluster *crv1.Pgcluster, deployment *appsv1.Deployment) error { +func UpdateExporterSidecar(clientset kubeapi.Interface, cluster *crv1.Pgcluster, deployment *appsv1.Deployment) error { // need to determine if we are adding or removing if cluster.Spec.Exporter { return addExporterSidecar(cluster, deployment) diff --git a/internal/operator/cluster/rolling.go b/internal/operator/cluster/rolling.go index feb2df24b1..2860db5fbd 100644 --- a/internal/operator/cluster/rolling.go +++ b/internal/operator/cluster/rolling.go @@ -26,6 +26,7 @@ import ( "github.com/crunchydata/postgres-operator/internal/operator" "github.com/crunchydata/postgres-operator/internal/util" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" + log "github.com/sirupsen/logrus" appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" @@ -68,8 +69,8 @@ const ( // Erroring during this process can be fun. If an error occurs within the middle // of a rolling update, in order to avoid placing the cluster in an // indeterminate state, most errors are just logged for later troubleshooting -func RollingUpdate(clientset kubernetes.Interface, restConfig *rest.Config, cluster *crv1.Pgcluster, - updateFunc func(*crv1.Pgcluster, *appsv1.Deployment) error) error { +func RollingUpdate(clientset kubeapi.Interface, restConfig *rest.Config, cluster *crv1.Pgcluster, + updateFunc func(kubeapi.Interface, *crv1.Pgcluster, *appsv1.Deployment) error) error { log.Debugf("rolling update for cluster %q", cluster.Name) // we need to determine which deployments are replicas and which is the @@ -147,13 +148,13 @@ func RollingUpdate(clientset kubernetes.Interface, restConfig *rest.Config, clus // instance. It first ensures that the update can be applied. If it can, it will // safely turn of the PostgreSQL instance before modifying the Deployment // template. -func applyUpdateToPostgresInstance(clientset kubernetes.Interface, restConfig *rest.Config, +func applyUpdateToPostgresInstance(clientset kubeapi.Interface, restConfig *rest.Config, cluster *crv1.Pgcluster, deployment appsv1.Deployment, - updateFunc func(*crv1.Pgcluster, *appsv1.Deployment) error) error { + updateFunc func(kubeapi.Interface, *crv1.Pgcluster, *appsv1.Deployment) error) error { ctx := context.TODO() // apply any updates, if they cannot be applied, then return an error here - if err := updateFunc(cluster, &deployment); err != nil { + if err := updateFunc(clientset, cluster, &deployment); err != nil { return err } diff --git a/internal/operator/clusterutilities.go b/internal/operator/clusterutilities.go index f96d5e5832..163850861e 100644 --- a/internal/operator/clusterutilities.go +++ b/internal/operator/clusterutilities.go @@ -189,6 +189,9 @@ type DeploymentTemplateFields struct { Tablespaces string TablespaceVolumes string TablespaceVolumeMounts string + // Tolerations is an optional parameter that provides Pod tolerations that + // have been transformed into JSON encoding from an actual Tolerations object + Tolerations string // The following fields set the TLS requirements as well as provide // information on how to configure TLS in a PostgreSQL cluster // TLSEnabled enables TLS in a cluster if set to true. Only works in actuality @@ -967,6 +970,25 @@ func GetSyncReplication(specSyncReplication *bool) bool { return false } +// GetTolerations returns any tolerations that may be defined in a tolerations +// in JSON format. Otherwise, it returns an empty string +func GetTolerations(tolerations []v1.Toleration) string { + // if no tolerations, exit early + if len(tolerations) == 0 { + return "" + } + + // turn into a JSON string + s, err := json.MarshalIndent(tolerations, "", " ") + + if err != nil { + log.Errorf("%s: returning empty string", err.Error()) + return "" + } + + return string(s) +} + // OverrideClusterContainerImages is a helper function that provides the // appropriate hooks to override any of the container images that might be // deployed with a PostgreSQL cluster diff --git a/pkg/apis/crunchydata.com/v1/cluster.go b/pkg/apis/crunchydata.com/v1/cluster.go index e2180e90dc..c487bc81b8 100644 --- a/pkg/apis/crunchydata.com/v1/cluster.go +++ b/pkg/apis/crunchydata.com/v1/cluster.go @@ -132,6 +132,10 @@ type PgclusterSpec struct { // Annotations contains a set of Deployment (and by association, Pod) // annotations that are propagated to all managed Deployments Annotations ClusterAnnotations `json:"annotations"` + + // Tolerations are an optional list of Pod toleration rules that are applied + // to the PostgreSQL instance. + Tolerations []v1.Toleration `json:"tolerations"` } // ClusterAnnotations provides a set of annotations that can be propagated to diff --git a/pkg/apis/crunchydata.com/v1/replica.go b/pkg/apis/crunchydata.com/v1/replica.go index 386fa033d0..1bfba208fe 100644 --- a/pkg/apis/crunchydata.com/v1/replica.go +++ b/pkg/apis/crunchydata.com/v1/replica.go @@ -16,6 +16,7 @@ package v1 */ import ( + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -42,6 +43,9 @@ type PgreplicaSpec struct { ReplicaStorage PgStorageSpec `json:"replicastorage"` Status string `json:"status"` UserLabels map[string]string `json:"userlabels"` + // Tolerations are an optional list of Pod toleration rules that are applied + // to the PostgreSQL instance. + Tolerations []v1.Toleration `json:"tolerations"` } // PgreplicaList ... diff --git a/pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go b/pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go index 69a3a673f5..3cef8c84f5 100644 --- a/pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go +++ b/pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go @@ -271,6 +271,13 @@ func (in *PgclusterSpec) DeepCopyInto(out *PgclusterSpec) { out.TLS = in.TLS out.PGDataSource = in.PGDataSource in.Annotations.DeepCopyInto(&out.Annotations) + if in.Tolerations != nil { + in, out := &in.Tolerations, &out.Tolerations + *out = make([]corev1.Toleration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -465,6 +472,13 @@ func (in *PgreplicaSpec) DeepCopyInto(out *PgreplicaSpec) { (*out)[key] = val } } + if in.Tolerations != nil { + in, out := &in.Tolerations, &out.Tolerations + *out = make([]corev1.Toleration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } diff --git a/pkg/apiservermsgs/clustermsgs.go b/pkg/apiservermsgs/clustermsgs.go index b995d0ea13..53258b36e6 100644 --- a/pkg/apiservermsgs/clustermsgs.go +++ b/pkg/apiservermsgs/clustermsgs.go @@ -17,6 +17,8 @@ limitations under the License. import ( crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" + + v1 "k8s.io/api/core/v1" ) // ShowClusterRequest shows cluster @@ -202,6 +204,8 @@ type CreateClusterRequest struct { PGDataSource crv1.PGDataSourceSpec // Annotations provide any custom annotations for a cluster Annotations crv1.ClusterAnnotations `json:"annotations"` + // Tolerations allows for the setting of Pod tolerations on Postgres instances + Tolerations []v1.Toleration `json:"tolerations"` } // CreateClusterDetail provides details about the PostgreSQL cluster that is @@ -559,6 +563,8 @@ type ClusterScaleRequest struct { // StorageConfig, if provided, specifies which of the storage configuration // options should be used. Defaults to what the main cluster definition uses. StorageConfig string `json:"storageConfig"` + // Tolerations allows for the setting of Pod tolerations on Postgres instances + Tolerations []v1.Toleration `json:"tolerations"` } // ClusterScaleResponse ... From 2249079a450d21b1062b2dd0bcf6826188ea76e0 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 27 Dec 2020 14:32:08 -0500 Subject: [PATCH 080/276] Allow for PostgreSQL user Secrets to be generated by Operator Previously, the PostgreSQL user Secrets required for bootstrap (superuser, replication user, standard user) needed to be present before adding a custom resource. Now, the PostgreSQL Operator can create the missing Secrets before bootstrapping a cluster. The API server behavior does not change, as the API server can both create the user credentials and return them to the end user. This also modifies the examples to not include a requirement to generate the replication user credential. As this is really a managed service credential, there is no reason to put this onus on the user. The superuser credential is still included in the exmaples, as there are certain situations where one should log in as the superuser. --- docs/content/custom-resources/_index.md | 141 ++++++++---------- .../primaryuser-secret.yaml | 11 -- examples/create-by-resource/run.sh | 1 - .../templates/primaryuser-secret.yaml | 12 -- examples/helm/create-cluster/values.yaml | 2 - examples/kustomize/createcluster/README.md | 16 +- .../createcluster/base/kustomization.yaml | 7 - .../createcluster/overlay/dev/devhippo.json | 3 +- .../createcluster/overlay/prod/prodhippo.json | 3 +- .../overlay/staging/staginghippo.json | 3 +- internal/operator/cluster/cluster.go | 66 ++++++++ 11 files changed, 139 insertions(+), 126 deletions(-) delete mode 100644 examples/create-by-resource/primaryuser-secret.yaml delete mode 100644 examples/helm/create-cluster/templates/primaryuser-secret.yaml diff --git a/docs/content/custom-resources/_index.md b/docs/content/custom-resources/_index.md index b323911e18..4c88e634ce 100644 --- a/docs/content/custom-resources/_index.md +++ b/docs/content/custom-resources/_index.md @@ -57,48 +57,6 @@ create additional secrets. The following guide goes through how to create a PostgreSQL cluster called `hippo` by creating a new custom resource. -#### Step 1: Creating the PostgreSQL User Secrets - -As mentioned above, there are a minimum of three PostgreSQL user accounts that -you must create in order to bootstrap a PostgreSQL cluster. These are: - -- A PostgreSQL superuser -- A replication user -- A standard PostgreSQL user - -The below code will help you set up these Secrets. - -``` -# this variable is the name of the cluster being created -pgo_cluster_name=hippo -# this variable is the namespace the cluster is being deployed into -cluster_namespace=pgo - -# this is the superuser secret -kubectl create secret generic -n "${cluster_namespace}" "${pgo_cluster_name}-postgres-secret" \ - --from-literal=username=postgres \ - --from-literal=password=Supersecurepassword* - -# this is the replication user secret -kubectl create secret generic -n "${cluster_namespace}" "${pgo_cluster_name}-primaryuser-secret" \ - --from-literal=username=primaryuser \ - --from-literal=password=Anothersecurepassword* - -# this is the standard user secret -kubectl create secret generic -n "${cluster_namespace}" "${pgo_cluster_name}-hippo-secret" \ - --from-literal=username=hippo \ - --from-literal=password=Moresecurepassword* - - -kubectl label secrets -n "${cluster_namespace}" "${pgo_cluster_name}-postgres-secret" "pg-cluster=${pgo_cluster_name}" -kubectl label secrets -n "${cluster_namespace}" "${pgo_cluster_name}-primaryuser-secret" "pg-cluster=${pgo_cluster_name}" -kubectl label secrets -n "${cluster_namespace}" "${pgo_cluster_name}-hippo-secret" "pg-cluster=${pgo_cluster_name}" -``` - -#### Step 2: Create the PostgreSQL Cluster - -With the Secrets in place. It is now time to create the PostgreSQL cluster. - The below manifest references the Secrets created in the previous step to add a custom resource to the `pgclusters.crunchydata.com` custom resource definition. @@ -121,7 +79,6 @@ metadata: autofail: "true" crunchy-pgbadger: "false" crunchy-pgha-scope: ${pgo_cluster_name} - crunchy-postgres-exporter: "false" deployment-name: ${pgo_cluster_name} name: ${pgo_cluster_name} pg-cluster: ${pgo_cluster_name} @@ -172,6 +129,7 @@ spec: clustername: ${pgo_cluster_name} customconfig: "" database: ${pgo_cluster_name} + exporter: false exporterport: "9187" limits: {} name: ${pgo_cluster_name} @@ -204,7 +162,6 @@ spec: tolerations: [] user: hippo userlabels: - crunchy-postgres-exporter: "false" pg-pod-anti-affinity: "" pgo-version: {{< param operatorVersion >}} usersecretname: ${pgo_cluster_name}-hippo-secret @@ -213,48 +170,47 @@ EOF kubectl apply -f "${pgo_cluster_name}-pgcluster.yaml" ``` -### Create a PostgreSQL Cluster With Backups in S3 +And that's all! The PostgreSQL Operator will go ahead and create the cluster. -A frequent use case is to create a PostgreSQL cluster with S3 or a S3-like -storage system for storing backups. This requires adding a Secret that contains -the S3 key and key secret for your account, and adding some additional -information into the custom resource. - -#### Step 1: Create the pgBackRest S3 Secrets +As part of this process, the PostgreSQL Operator creates several Secrets that +contain the credentials for three user accounts that must be present in order +to bootstrap a PostgreSQL cluster. These are: -As mentioned above, it is necessary to create a Secret containing the S3 key and -key secret that will allow a user to create backups in S3. +- A PostgreSQL superuser +- A replication user +- A standard PostgreSQL user -The below code will help you set up this Secret. +The Secrets represent the following PostgreSQL users and can be identified using +the below patterns: -``` -# this variable is the name of the cluster being created -pgo_cluster_name=hippo -# this variable is the namespace the cluster is being deployed into -cluster_namespace=pgo -# the following variables are your S3 key and key secret -backrest_s3_key=yours3key -backrest_s3_key_secret=yours3keysecret +| PostgreSQL User | Type | Secret Pattern | Notes | +| --------------- | ----------- | ---------------------------------- | ----- | +| `postgres` | Superuser | `-postgres-secret` | This is the PostgreSQL superuser account. Using the above example, the name of the secret would be `hippo-postgres-secret`. | +| `primaryuser` | Replication | `-primaryuser-secret` | This is for the managed replication user account for maintaining high availability. This account does not need to be accessed. Using the above example, the name of the secret would be `hippo-primaryuser-secret`. | +| User | User | `--secret` | This is an unprivileged user that should be used for most operations. This secret is set by the `user` attribute in the custom resources. In the above example, the name of this user is `hippo`, which would make the Secret `hippo-hippo-secret` | -kubectl -n "${cluster_namespace}" create secret generic "${pgo_cluster_name}-backrest-repo-config" \ - --from-literal="aws-s3-key=${backrest_s3_key}" \ - --from-literal="aws-s3-key-secret=${backrest_s3_key_secret}" +To extract the user credentials so you can log into the database, you can use +the following JSONPath expression: -unset backrest_s3_key -unset backrest_s3_key_secret ``` +# namespace that the cluster is running in +export cluster_namespace=pgo +# name of the cluster +export pgo_cluster_name=hippo +# name of the user whose password we want to get +export pgo_cluster_username=hippo -#### Step 2: Creating the PostgreSQL User Secrets - -Similar to the basic create cluster example, there are a minimum of three -PostgreSQL user accounts that you must create in order to bootstrap a PostgreSQL -cluster. These are: +kubectl -n "${cluster_namespace}" get secrets \ + "${pgo_cluster_name}-${pgo_cluster_username}-secret" -o "jsonpath={.data['password']}" | base64 -d +``` -- A PostgreSQL superuser -- A replication user -- A standard PostgreSQL user +#### Customizing User Credentials -The below code will help you set up these Secrets. +If you wish to set the credentials for these users on your own, you have to +create these Secrets _before_ creating a custom resource. The below example +shows how to create the three required user accounts prior to creating a custom +resource. Note that if you omit any of these Secrets, the Postgres Operator +will create it on its own. ``` # this variable is the name of the cluster being created @@ -283,7 +239,38 @@ kubectl label secrets -n "${cluster_namespace}" "${pgo_cluster_name}-primaryuser kubectl label secrets -n "${cluster_namespace}" "${pgo_cluster_name}-hippo-secret" "pg-cluster=${pgo_cluster_name}" ``` -#### Step 3: Create the PostgreSQL Cluster +### Create a PostgreSQL Cluster With Backups in S3 + +A frequent use case is to create a PostgreSQL cluster with S3 or a S3-like +storage system for storing backups. This requires adding a Secret that contains +the S3 key and key secret for your account, and adding some additional +information into the custom resource. + +#### Step 1: Create the pgBackRest S3 Secrets + +As mentioned above, it is necessary to create a Secret containing the S3 key and +key secret that will allow a user to create backups in S3. + +The below code will help you set up this Secret. + +``` +# this variable is the name of the cluster being created +pgo_cluster_name=hippo +# this variable is the namespace the cluster is being deployed into +cluster_namespace=pgo +# the following variables are your S3 key and key secret +backrest_s3_key=yours3key +backrest_s3_key_secret=yours3keysecret + +kubectl -n "${cluster_namespace}" create secret generic "${pgo_cluster_name}-backrest-repo-config" \ + --from-literal="aws-s3-key=${backrest_s3_key}" \ + --from-literal="aws-s3-key-secret=${backrest_s3_key_secret}" + +unset backrest_s3_key +unset backrest_s3_key_secret +``` + +#### Step 2: Create the PostgreSQL Cluster With the Secrets in place. It is now time to create the PostgreSQL cluster. diff --git a/examples/create-by-resource/primaryuser-secret.yaml b/examples/create-by-resource/primaryuser-secret.yaml deleted file mode 100644 index 15ee8ad665..0000000000 --- a/examples/create-by-resource/primaryuser-secret.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: v1 -data: - password: d0ZvYWlRZFhPTQ== - username: cHJpbWFyeXVzZXI= -kind: Secret -metadata: - labels: - pg-cluster: fromcrd - name: fromcrd-primaryuser-secret - namespace: pgouser1 -type: Opaque diff --git a/examples/create-by-resource/run.sh b/examples/create-by-resource/run.sh index e6940ead12..98bd67ec2c 100755 --- a/examples/create-by-resource/run.sh +++ b/examples/create-by-resource/run.sh @@ -45,7 +45,6 @@ rm $DIR/fromcrd-key $DIR/fromcrd-key.pub # create the required postgres credentials for the fromcrd cluster $PGO_CMD -n $NS create -f $DIR/postgres-secret.yaml -$PGO_CMD -n $NS create -f $DIR/primaryuser-secret.yaml $PGO_CMD -n $NS create -f $DIR/testuser-secret.yaml # create the pgcluster CRD for the fromcrd cluster diff --git a/examples/helm/create-cluster/templates/primaryuser-secret.yaml b/examples/helm/create-cluster/templates/primaryuser-secret.yaml deleted file mode 100644 index f4471b8fd2..0000000000 --- a/examples/helm/create-cluster/templates/primaryuser-secret.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: v1 -data: - password: {{ .Values.primaryusersecretpassword | b64enc }} - username: {{ .Values.primaryusersecretuser | b64enc }} -kind: Secret -metadata: - labels: - pg-cluster: {{ .Values.pgclustername }} - vendor: crunchydata - name: {{ .Values.pgclustername }}-primaryuser-secret - namespace: {{ .Values.namespace }} -type: Opaque \ No newline at end of file diff --git a/examples/helm/create-cluster/values.yaml b/examples/helm/create-cluster/values.yaml index 4add0e560f..b0301c6205 100644 --- a/examples/helm/create-cluster/values.yaml +++ b/examples/helm/create-cluster/values.yaml @@ -13,5 +13,3 @@ hipposecretuser: "hippo" hipposecretpassword: "Supersecurepassword*" postgressecretuser: "postgres" postgressecretpassword: "Anothersecurepassword*" -primaryusersecretuser: "primaryuser" -primaryusersecretpassword: "Moresecurepassword*" \ No newline at end of file diff --git a/examples/kustomize/createcluster/README.md b/examples/kustomize/createcluster/README.md index 0bfc762305..f21cae2f90 100644 --- a/examples/kustomize/createcluster/README.md +++ b/examples/kustomize/createcluster/README.md @@ -34,7 +34,6 @@ You will see these items are created after running the above command ``` secret/hippo-hippo-secret created secret/hippo-postgres-secret created -secret/hippo-primaryuser-secret created pgcluster.crunchydata.com/hippo created ``` You may need to wait a few seconds depending on the resources you have allocated to you kubernetes set up for the Crunchy PostgreSQL cluster to become available. @@ -51,7 +50,7 @@ cluster : hippo (crunchy-postgres-ha:centos7-12.5-4.5.1) deployment : hippo deployment : hippo-backrest-shared-repo service : hippo - ClusterIP (10.0.56.86) - Ports (2022/TCP, 5432/TCP) - labels : pg-pod-anti-affinity= pgo-backrest=true pgo-version=4.5.1 crunchy-postgres-exporter=false name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata autofail=true crunchy-pgbadger=false + labels : pg-pod-anti-affinity= pgo-backrest=true pgo-version=4.5.1 crunchy-postgres-exporter=false name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata autofail=true crunchy-pgbadger=false ``` Feel free to run other pgo cli commands on the hippo cluster @@ -72,7 +71,6 @@ You will see these items are created after running the above command ``` secret/dev-hippo-hippo-secret created secret/dev-hippo-postgres-secret created -secret/dev-hippo-primaryuser-secret created pgcluster.crunchydata.com/dev-hippo created ``` After the cluster is finished creating lets take a look at the cluster with the Crunchy PostgreSQL Operator @@ -89,7 +87,7 @@ cluster : dev-hippo (crunchy-postgres-ha:centos7-12.5-4.5.1) deployment : dev-hippo-pgbouncer service : dev-hippo - ClusterIP (10.0.62.87) - Ports (2022/TCP, 5432/TCP) service : dev-hippo-pgbouncer - ClusterIP (10.0.48.120) - Ports (5432/TCP) - labels : crunchy-pgha-scope=dev-hippo crunchy-postgres-exporter=false name=dev-hippo pg-cluster=dev-hippo pg-pod-anti-affinity= pgo-backrest=true vendor=crunchydata autofail=true crunchy-pgbadger=false deployment-name=dev-hippo environment=development pgo-version=4.5.1 pgouser=admin + labels : crunchy-pgha-scope=dev-hippo crunchy-postgres-exporter=false name=dev-hippo pg-cluster=dev-hippo pg-pod-anti-affinity= pgo-backrest=true vendor=crunchydata autofail=true crunchy-pgbadger=false deployment-name=dev-hippo environment=development pgo-version=4.5.1 pgouser=admin ``` #### staging The staging overlay will deploy a crunchy postgreSQL cluster with 2 replica's with annotations added @@ -106,7 +104,6 @@ You will see these items are created after running the above command ``` secret/staging-hippo-hippo-secret created secret/staging-hippo-postgres-secret created -secret/staging-hippo-primaryuser-secret created pgcluster.crunchydata.com/staging-hippo created pgreplica.crunchydata.com/staging-hippo-rpl1 created ``` @@ -131,11 +128,11 @@ cluster : staging-hippo (crunchy-postgres-ha:centos7-12.5-4.5.1) service : staging-hippo-replica - ClusterIP (10.0.56.57) - Ports (2022/TCP, 5432/TCP) pgreplica : staging-hippo-lnxw pgreplica : staging-hippo-rpl1 - labels : deployment-name=staging-hippo environment=staging name=staging-hippo pg-pod-anti-affinity= crunchy-postgres-exporter=false crunchy-pgbadger=false crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-backrest=true pgo-version=4.5.1 pgouser=admin vendor=crunchydata autofail=true + labels : deployment-name=staging-hippo environment=staging name=staging-hippo pg-pod-anti-affinity= crunchy-postgres-exporter=false crunchy-pgbadger=false crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-backrest=true pgo-version=4.5.1 pgouser=admin vendor=crunchydata autofail=true ``` #### production -The production overlay will deploy a crunchy postgreSQL cluster with one replica +The production overlay will deploy a crunchy postgreSQL cluster with one replica Lets generate the kustomize yaml for the prod overlay ``` @@ -149,7 +146,6 @@ You will see these items are created after running the above command ``` secret/prod-hippo-hippo-secret created secret/prod-hippo-postgres-secret created -secret/prod-hippo-primaryuser-secret created pgcluster.crunchydata.com/prod-hippo created ``` After the cluster is finished creating lets take a look at the cluster with the crunchy postgreSQL operator @@ -169,7 +165,7 @@ cluster : prod-hippo (crunchy-postgres-ha:centos7-12.5-4.5.1) service : prod-hippo - ClusterIP (10.0.56.18) - Ports (2022/TCP, 5432/TCP) service : prod-hippo-replica - ClusterIP (10.0.56.101) - Ports (2022/TCP, 5432/TCP) pgreplica : prod-hippo-flty - labels : pgo-backrest=true pgo-version=4.5.1 crunchy-pgbadger=false crunchy-postgres-exporter=false deployment-name=prod-hippo environment=production pg-cluster=prod-hippo pg-pod-anti-affinity= autofail=true crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata + labels : pgo-backrest=true pgo-version=4.5.1 crunchy-pgbadger=false crunchy-postgres-exporter=false deployment-name=prod-hippo environment=production pg-cluster=prod-hippo pg-pod-anti-affinity= autofail=true crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata ``` ### Delete the clusters To delete the clusters run the following pgo cli commands @@ -184,4 +180,4 @@ pgo delete cluster hippo -n pgo pgo delete cluster dev-hippo -n pgo pgo delete cluster staging-hippo -n pgo pgo delete cluster prod-hippo -n pgo -``` \ No newline at end of file +``` diff --git a/examples/kustomize/createcluster/base/kustomization.yaml b/examples/kustomize/createcluster/base/kustomization.yaml index a93d6f8eaa..91fb2a0954 100644 --- a/examples/kustomize/createcluster/base/kustomization.yaml +++ b/examples/kustomize/createcluster/base/kustomization.yaml @@ -10,12 +10,6 @@ secretGenerator: literals: - username=hippo - password=Moresecurepassword* - - name: hippo-primaryuser-secret - options: - disableNameSuffixHash: true - literals: - - username=primaryuser - - password=Anothersecurepassword* - name: hippo-postgres-secret options: disableNameSuffixHash: true @@ -24,4 +18,3 @@ secretGenerator: - password=Supersecurepassword* resources: - pgcluster.yaml - diff --git a/examples/kustomize/createcluster/overlay/dev/devhippo.json b/examples/kustomize/createcluster/overlay/dev/devhippo.json index ab7c2e5071..c25b2085ea 100644 --- a/examples/kustomize/createcluster/overlay/dev/devhippo.json +++ b/examples/kustomize/createcluster/overlay/dev/devhippo.json @@ -12,7 +12,6 @@ { "op": "replace", "path": "/spec/clustername", "value": "dev-hippo" }, { "op": "replace", "path": "/spec/database", "value": "dev-hippo" }, { "op": "replace", "path": "/spec/name", "value": "dev-hippo" }, - { "op": "replace", "path": "/spec/primarysecretname", "value": "dev-hippo-primaryuser-secret" }, { "op": "replace", "path": "/spec/rootsecretname", "value": "dev-hippo-postgres-secret" }, { "op": "replace", "path": "/spec/usersecretname", "value": "dev-hippo-hippo-secret" } -] \ No newline at end of file +] diff --git a/examples/kustomize/createcluster/overlay/prod/prodhippo.json b/examples/kustomize/createcluster/overlay/prod/prodhippo.json index ef8313629d..2a595bc24b 100644 --- a/examples/kustomize/createcluster/overlay/prod/prodhippo.json +++ b/examples/kustomize/createcluster/overlay/prod/prodhippo.json @@ -12,8 +12,7 @@ { "op": "replace", "path": "/spec/clustername", "value": "prod-hippo" }, { "op": "replace", "path": "/spec/database", "value": "prod-hippo" }, { "op": "replace", "path": "/spec/name", "value": "prod-hippo" }, - { "op": "replace", "path": "/spec/primarysecretname", "value": "prod-hippo-primaryuser-secret" }, { "op": "replace", "path": "/spec/replicas", "value": "1"}, { "op": "replace", "path": "/spec/rootsecretname", "value": "prod-hippo-postgres-secret" }, { "op": "replace", "path": "/spec/usersecretname", "value": "prod-hippo-hippo-secret" } -] \ No newline at end of file +] diff --git a/examples/kustomize/createcluster/overlay/staging/staginghippo.json b/examples/kustomize/createcluster/overlay/staging/staginghippo.json index c19acb5895..0b811d3b45 100644 --- a/examples/kustomize/createcluster/overlay/staging/staginghippo.json +++ b/examples/kustomize/createcluster/overlay/staging/staginghippo.json @@ -12,8 +12,7 @@ { "op": "replace", "path": "/spec/clustername", "value": "staging-hippo" }, { "op": "replace", "path": "/spec/database", "value": "staging-hippo" }, { "op": "replace", "path": "/spec/name", "value": "staging-hippo" }, - { "op": "replace", "path": "/spec/primarysecretname", "value": "staging-hippo-primaryuser-secret" }, { "op": "replace", "path": "/spec/replicas", "value": "1"}, { "op": "replace", "path": "/spec/rootsecretname", "value": "staging-hippo-postgres-secret" }, { "op": "replace", "path": "/spec/usersecretname", "value": "staging-hippo-hippo-secret" } -] \ No newline at end of file +] diff --git a/internal/operator/cluster/cluster.go b/internal/operator/cluster/cluster.go index e01b3a837b..b71aac778f 100644 --- a/internal/operator/cluster/cluster.go +++ b/internal/operator/cluster/cluster.go @@ -74,6 +74,14 @@ func AddClusterBase(clientset kubeapi.Interface, cl *crv1.Pgcluster, namespace s return } + // create any missing user secrets that are required to be part of the + // bootstrap + if err := createMissingUserSecrets(clientset, cl); err != nil { + log.Errorf("error creating missing user secrets: %q", err.Error()) + publishClusterCreateFailure(cl, err.Error()) + return + } + if err = addClusterCreateMissingService(clientset, cl, namespace); err != nil { log.Error("error in creating primary service " + err.Error()) publishClusterCreateFailure(cl, err.Error()) @@ -686,6 +694,64 @@ func annotateBackrestSecret(clientset kubernetes.Interface, cluster *crv1.Pgclus return err } +// createMissingUserSecret is the heart of trying to determine if a user secret +// is missing, and if it is, creating it. Requires the appropriate secretName +// suffix for a given secret, as well as the user name +// createUserSecret(request, newInstance, crv1.RootSecretSuffix, +// crv1.PGUserSuperuser, request.PasswordSuperuser) +func createMissingUserSecret(clientset kubernetes.Interface, cluster *crv1.Pgcluster, + secretNameSuffix, username string) error { + ctx := context.TODO() + + // the secretName is just the combination cluster name and the + // secretNameSuffix + secretName := cluster.Spec.Name + secretNameSuffix + + // if the secret already exists, skip it + // if it returns an error other than "not found" return an error + if _, err := clientset.CoreV1().Secrets(cluster.Spec.Namespace).Get( + ctx, secretName, metav1.GetOptions{}); err == nil { + log.Infof("user secret %q exists for user %q for cluster %q", + secretName, username, cluster.Spec.Name) + return nil + } else if !kerrors.IsNotFound(err) { + return err + } + + // alright, so we have to create the secret + // if the password fails to generate, return an error + passwordLength := util.GeneratedPasswordLength(operator.Pgo.Cluster.PasswordLength) + password, err := util.GeneratePassword(passwordLength) + if err != nil { + return err + } + + // great, now we can create the secret! if we can't, return an error + return util.CreateSecret(clientset, cluster.Spec.Name, secretName, + username, password, cluster.Spec.Namespace) +} + +// createMissingUserSecrets checks to see if there are secrets for the +// superuser (postgres), replication user (primaryuser), and a standard postgres +// user for the given cluster. Each of these are created if they do not +// currently exist +func createMissingUserSecrets(clientset kubernetes.Interface, cluster *crv1.Pgcluster) error { + // first, determine if we need to create a user secret for the postgres + // superuser + if err := createMissingUserSecret(clientset, cluster, crv1.RootSecretSuffix, crv1.PGUserSuperuser); err != nil { + return err + } + + // next, determine if we need to create a user secret for the replication user + if err := createMissingUserSecret(clientset, cluster, crv1.PrimarySecretSuffix, crv1.PGUserReplication); err != nil { + return err + } + + // finally, determine if we need to create a user secret for the regular user + userSecretSuffix := fmt.Sprintf("-%s%s", cluster.Spec.User, crv1.UserSecretSuffix) + return createMissingUserSecret(clientset, cluster, userSecretSuffix, cluster.Spec.User) +} + func deleteConfigMaps(clientset kubernetes.Interface, clusterName, ns string) error { ctx := context.TODO() label := fmt.Sprintf("pg-cluster=%s", clusterName) From 11375e22483a6bd5316e4d52db4e03fcb7857533 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 27 Dec 2020 16:11:06 -0500 Subject: [PATCH 081/276] Eliminate user Secret name attributes from pgcluster CRD These Secrets have been fully managed by the Postgres Operator for a long time and there is no reason why they need to be specified by the user as specific custom resource attributes. This further simplifies the number of steps required to create a PostgreSQL cluster with the operator through a "custom resource" workflow. --- docs/content/custom-resources/_index.md | 10 -- examples/create-by-resource/fromcrd.json | 5 +- .../create-cluster/templates/pgcluster.yaml | 3 - .../createcluster/base/pgcluster.yaml | 3 - .../createcluster/overlay/dev/devhippo.json | 4 +- .../createcluster/overlay/prod/prodhippo.json | 4 +- .../overlay/staging/staginghippo.json | 4 +- .../files/crds/pgclusters-crd.yaml | 3 - .../postgresoperator.crd.descriptions.yaml | 16 --- .../olm/postgresoperator.crd.examples.yaml | 3 - installers/olm/postgresoperator.crd.yaml | 3 - .../apiserver/clusterservice/clusterimpl.go | 104 +++++++++--------- .../apiserver/scheduleservice/scheduleimpl.go | 2 +- internal/apiserver/userservice/userimpl.go | 21 +--- internal/operator/cluster/cluster.go | 15 +-- internal/operator/cluster/clusterlogic.go | 12 +- internal/util/secrets.go | 19 ++-- pkg/apis/crunchydata.com/v1/cluster.go | 23 +++- pkg/apis/crunchydata.com/v1/cluster_test.go | 75 +++++++++++++ pkg/apis/crunchydata.com/v1/common.go | 9 -- 20 files changed, 177 insertions(+), 161 deletions(-) create mode 100644 pkg/apis/crunchydata.com/v1/cluster_test.go diff --git a/docs/content/custom-resources/_index.md b/docs/content/custom-resources/_index.md index 4c88e634ce..a9f5879c4f 100644 --- a/docs/content/custom-resources/_index.md +++ b/docs/content/custom-resources/_index.md @@ -148,9 +148,7 @@ spec: pgBouncer: preferred policies: "" port: "5432" - primarysecretname: ${pgo_cluster_name}-primaryuser-secret replicas: "0" - rootsecretname: ${pgo_cluster_name}-postgres-secret shutdown: false standby: false tablespaceMounts: {} @@ -164,7 +162,6 @@ spec: userlabels: pg-pod-anti-affinity: "" pgo-version: {{< param operatorVersion >}} - usersecretname: ${pgo_cluster_name}-hippo-secret EOF kubectl apply -f "${pgo_cluster_name}-pgcluster.yaml" @@ -369,9 +366,7 @@ spec: pgBouncer: preferred policies: "" port: "5432" - primarysecretname: ${pgo_cluster_name}-primaryuser-secret replicas: "0" - rootsecretname: ${pgo_cluster_name}-postgres-secret shutdown: false standby: false tablespaceMounts: {} @@ -386,7 +381,6 @@ spec: backrest-storage-type: "s3" pg-pod-anti-affinity: "" pgo-version: {{< param operatorVersion >}} - usersecretname: ${pgo_cluster_name}-hippo-secret EOF kubectl apply -f "${pgo_cluster_name}-pgcluster.yaml" @@ -689,16 +683,12 @@ make changes, as described below. | PodAntiAffinity | `create` | A required section. Sets the [pod anti-affinity rules]({{< relref "/architecture/high-availability/_index.md#how-the-crunchy-postgresql-operator-uses-pod-anti-affinity" >}}) for the PostgreSQL cluster and associated deployments. Please see the `Pod Anti-Affinity Specification` section below. | | Policies | `create` | If provided, a comma-separated list referring to `pgpolicies.crunchydata.com.Spec.Name` that should be run once the PostgreSQL primary is first initialized. | | Port | `create` | The port that PostgreSQL will run on, e.g. `5432`. | -| PrimaryStorage | `create` | A specification that gives information about the storage attributes for the primary instance in the PostgreSQL cluster. For details, please see the `Storage Specification` section below. This is required. | -| RootSecretName | `create` | The name of a Kubernetes Secret that contains the credentials for a PostgreSQL _replication user_ that is created when the PostgreSQL cluster is first bootstrapped. For more information, please see `User Secret Specification`.| | ReplicaStorage | `create` | A specification that gives information about the storage attributes for any replicas in the PostgreSQL cluster. For details, please see the `Storage Specification` section below. This will likely be changed in the future based on the nature of the high-availability system, but presently it is still required that you set it. It is recommended you use similar settings to that of `PrimaryStorage`. | | Replicas | `create` | The number of replicas to create after a PostgreSQL primary is first initialized. This only works on create; to scale a cluster after it is initialized, please use the [`pgo scale`]({{< relref "/pgo-client/reference/pgo_scale.md" >}}) command. | | Resources | `create`, `update` | Specify the container resource requests that the PostgreSQL cluster should use. Follows the [Kubernetes definitions of resource requests](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | -| RootSecretName | `create` | The name of a Kubernetes Secret that contains the credentials for a PostgreSQL superuser that is created when the PostgreSQL cluster is first bootstrapped. For more information, please see `User Secret Specification`.| | SyncReplication | `create` | If set to `true`, specifies the PostgreSQL cluster to use [synchronous replication]({{< relref "/architecture/high-availability/_index.md#how-the-crunchy-postgresql-operator-uses-pod-anti-affinity#synchronous-replication-guarding-against-transactions-loss" >}}).| | User | `create` | The name of the PostgreSQL user that is created when the PostgreSQL cluster is first created. | | UserLabels | `create` | A set of key-value string pairs that are used as a sort of "catch-all" for things that really should be modeled in the CRD. These values do get copied to the actually CR labels. If you want to set up metrics collection or pgBadger, you would specify `"crunchy-postgres-exporter": "true"` and `"crunchy-pgbadger": "true"` here, respectively. However, this structure does need to be set, so just follow whatever is in the example. | -| UserSecretName | `create` | The name of a Kubernetes Secret that contains the credentials for a standard PostgreSQL user that is created when the PostgreSQL cluster is first bootstrapped. For more information, please see `User Secret Specification`.| | TablespaceMounts | `create`,`update` | Lists any tablespaces that are attached to the PostgreSQL cluster. Tablespaces can be added at a later time by updating the `TablespaceMounts` entry, but they cannot be removed. Stores a map of information, with the key being the name of the tablespace, and the value being a Storage Specification, defined below. | | TLS | `create` | Defines the attributes for enabling TLS for a PostgreSQL cluster. See TLS Specification below. | | TLSOnly | `create` | If set to true, requires client connections to use only TLS to connect to the PostgreSQL database. | diff --git a/examples/create-by-resource/fromcrd.json b/examples/create-by-resource/fromcrd.json index 58dd2f515e..8713deb719 100644 --- a/examples/create-by-resource/fromcrd.json +++ b/examples/create-by-resource/fromcrd.json @@ -72,9 +72,7 @@ }, "policies": "", "port": "5432", - "primarysecretname": "fromcrd-primaryuser-secret", "replicas": "0", - "rootsecretname": "fromcrd-postgres-secret", "secretfrom": "", "shutdown": false, "standby": false, @@ -89,7 +87,6 @@ "pgo-version": "4.5.1", "pgouser": "pgoadmin", "pgo-backrest": "true" - }, - "usersecretname": "fromcrd-testuser-secret" + } } } diff --git a/examples/helm/create-cluster/templates/pgcluster.yaml b/examples/helm/create-cluster/templates/pgcluster.yaml index 0852b1f447..65f12d6460 100644 --- a/examples/helm/create-cluster/templates/pgcluster.yaml +++ b/examples/helm/create-cluster/templates/pgcluster.yaml @@ -76,9 +76,7 @@ spec: pgBouncer: preferred policies: "" port: "5432" - primarysecretname: {{ .Values.pgclustername }}-primaryuser-secret replicas: "0" - rootsecretname: {{ .Values.pgclustername }}-postgres-secret shutdown: false standby: false tablespaceMounts: {} @@ -92,4 +90,3 @@ spec: crunchy-postgres-exporter: "false" pg-pod-anti-affinity: "" pgo-version: {{ .Values.pgoversion }} - usersecretname: {{ .Values.pgclustername }}-hippo-secret diff --git a/examples/kustomize/createcluster/base/pgcluster.yaml b/examples/kustomize/createcluster/base/pgcluster.yaml index 29aa0c6e83..975a7ddf5d 100644 --- a/examples/kustomize/createcluster/base/pgcluster.yaml +++ b/examples/kustomize/createcluster/base/pgcluster.yaml @@ -86,9 +86,7 @@ spec: pgBouncer: preferred policies: "" port: "5432" - primarysecretname: hippo-primaryuser-secret replicas: "0" - rootsecretname: hippo-postgres-secret shutdown: false standby: false tablespaceMounts: {} @@ -102,4 +100,3 @@ spec: crunchy-postgres-exporter: "false" pg-pod-anti-affinity: "" pgo-version: 4.5.1 - usersecretname: hippo-hippo-secret diff --git a/examples/kustomize/createcluster/overlay/dev/devhippo.json b/examples/kustomize/createcluster/overlay/dev/devhippo.json index c25b2085ea..843e9c2c80 100644 --- a/examples/kustomize/createcluster/overlay/dev/devhippo.json +++ b/examples/kustomize/createcluster/overlay/dev/devhippo.json @@ -11,7 +11,5 @@ { "op": "replace", "path": "/spec/PrimaryStorage/name", "value": "dev-hippo" }, { "op": "replace", "path": "/spec/clustername", "value": "dev-hippo" }, { "op": "replace", "path": "/spec/database", "value": "dev-hippo" }, - { "op": "replace", "path": "/spec/name", "value": "dev-hippo" }, - { "op": "replace", "path": "/spec/rootsecretname", "value": "dev-hippo-postgres-secret" }, - { "op": "replace", "path": "/spec/usersecretname", "value": "dev-hippo-hippo-secret" } + { "op": "replace", "path": "/spec/name", "value": "dev-hippo" } ] diff --git a/examples/kustomize/createcluster/overlay/prod/prodhippo.json b/examples/kustomize/createcluster/overlay/prod/prodhippo.json index 2a595bc24b..76fd528ac0 100644 --- a/examples/kustomize/createcluster/overlay/prod/prodhippo.json +++ b/examples/kustomize/createcluster/overlay/prod/prodhippo.json @@ -12,7 +12,5 @@ { "op": "replace", "path": "/spec/clustername", "value": "prod-hippo" }, { "op": "replace", "path": "/spec/database", "value": "prod-hippo" }, { "op": "replace", "path": "/spec/name", "value": "prod-hippo" }, - { "op": "replace", "path": "/spec/replicas", "value": "1"}, - { "op": "replace", "path": "/spec/rootsecretname", "value": "prod-hippo-postgres-secret" }, - { "op": "replace", "path": "/spec/usersecretname", "value": "prod-hippo-hippo-secret" } + { "op": "replace", "path": "/spec/replicas", "value": "1"} ] diff --git a/examples/kustomize/createcluster/overlay/staging/staginghippo.json b/examples/kustomize/createcluster/overlay/staging/staginghippo.json index 0b811d3b45..7a5a9ab23f 100644 --- a/examples/kustomize/createcluster/overlay/staging/staginghippo.json +++ b/examples/kustomize/createcluster/overlay/staging/staginghippo.json @@ -12,7 +12,5 @@ { "op": "replace", "path": "/spec/clustername", "value": "staging-hippo" }, { "op": "replace", "path": "/spec/database", "value": "staging-hippo" }, { "op": "replace", "path": "/spec/name", "value": "staging-hippo" }, - { "op": "replace", "path": "/spec/replicas", "value": "1"}, - { "op": "replace", "path": "/spec/rootsecretname", "value": "staging-hippo-postgres-secret" }, - { "op": "replace", "path": "/spec/usersecretname", "value": "staging-hippo-hippo-secret" } + { "op": "replace", "path": "/spec/replicas", "value": "1"} ] diff --git a/installers/ansible/roles/pgo-operator/files/crds/pgclusters-crd.yaml b/installers/ansible/roles/pgo-operator/files/crds/pgclusters-crd.yaml index bea777b436..92c9fd7cd5 100644 --- a/installers/ansible/roles/pgo-operator/files/crds/pgclusters-crd.yaml +++ b/installers/ansible/roles/pgo-operator/files/crds/pgclusters-crd.yaml @@ -24,12 +24,9 @@ spec: exporterport: { type: string } name: { type: string } pgbadgerport: { type: string } - primarysecretname: { type: string } PrimaryStorage: { type: object } port: { type: string } - rootsecretname: { type: string } userlabels: { type: object } - usersecretname: { type: string } status: properties: state: { type: string } diff --git a/installers/olm/postgresoperator.crd.descriptions.yaml b/installers/olm/postgresoperator.crd.descriptions.yaml index 5d76dd4e0c..15c5b274ba 100644 --- a/installers/olm/postgresoperator.crd.descriptions.yaml +++ b/installers/olm/postgresoperator.crd.descriptions.yaml @@ -56,22 +56,6 @@ x-descriptors: - 'urn:alm:descriptor:com.tectonic.ui:number' - - path: rootsecretname - displayName: PostgreSQL superuser credentials - description: The name of the Secret that contains the PostgreSQL superuser credentials - x-descriptors: - - 'urn:alm:descriptor:io.kubernetes:Secret' - - path: primarysecretname - displayName: PostgreSQL support service credentials - description: The name of the Secret that contains the credentials used for managing cluster instance authentication, e.g. connections for replicas - x-descriptors: - - 'urn:alm:descriptor:io.kubernetes:Secret' - - path: usersecretname - displayName: PostgreSQL user credentials - description: The name of the Secret that contains the PostgreSQL user credentials for logging into the PostgreSQL cluster - x-descriptors: - - 'urn:alm:descriptor:io.kubernetes:Secret' - # `operator-sdk scorecard` expects this field to have a descriptor. - path: PrimaryStorage displayName: PostgreSQL Primary Storage diff --git a/installers/olm/postgresoperator.crd.examples.yaml b/installers/olm/postgresoperator.crd.examples.yaml index d7783c2707..49b58fac6c 100644 --- a/installers/olm/postgresoperator.crd.examples.yaml +++ b/installers/olm/postgresoperator.crd.examples.yaml @@ -18,9 +18,6 @@ spec: exporterport: '9187' pgbadgerport: '10000' port: '5432' - primarysecretname: example-primaryuser - rootsecretname: example-postgresuser - usersecretname: example-primaryuser userlabels: { archive: 'false' } --- diff --git a/installers/olm/postgresoperator.crd.yaml b/installers/olm/postgresoperator.crd.yaml index f39ac244fc..9d9da35997 100644 --- a/installers/olm/postgresoperator.crd.yaml +++ b/installers/olm/postgresoperator.crd.yaml @@ -24,13 +24,10 @@ spec: exporterport: { type: string } name: { type: string } pgbadgerport: { type: string } - primarysecretname: { type: string } PrimaryStorage: { type: object } port: { type: string } - rootsecretname: { type: string } status: { type: string } userlabels: { type: object } - usersecretname: { type: string } status: properties: state: { type: string } diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index 21fc5f24ba..43106d7dba 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -886,10 +886,10 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. newInstance.ObjectMeta.Labels[config.LABEL_PGOUSER] = pgouser if request.SecretFrom != "" { - err = validateSecretFrom(request.SecretFrom, newInstance.Spec.User, ns) + err = validateSecretFrom(newInstance, request.SecretFrom) if err != nil { resp.Status.Code = msgs.Error - resp.Status.Msg = request.SecretFrom + " secret was not found " + resp.Status.Msg = err.Error() return resp } } @@ -898,15 +898,12 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. // create the user secrets // first, the superuser - if secretName, password, err := createUserSecret(request, newInstance, crv1.RootSecretSuffix, - crv1.PGUserSuperuser, request.PasswordSuperuser); err != nil { + if password, err := createUserSecret(request, newInstance, crv1.PGUserSuperuser, request.PasswordSuperuser); err != nil { log.Error(err) resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() return resp } else { - newInstance.Spec.RootSecretName = secretName - // if the user requests to show system accounts, append it to the list if request.ShowSystemAccounts { user := msgs.CreateClusterDetailUser{ @@ -919,15 +916,12 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. } // next, the replication user - if secretName, password, err := createUserSecret(request, newInstance, crv1.PrimarySecretSuffix, - crv1.PGUserReplication, request.PasswordReplication); err != nil { + if password, err := createUserSecret(request, newInstance, crv1.PGUserReplication, request.PasswordReplication); err != nil { log.Error(err) resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() return resp } else { - newInstance.Spec.PrimarySecretName = secretName - // if the user requests to show system accounts, append it to the list if request.ShowSystemAccounts { user := msgs.CreateClusterDetailUser{ @@ -940,16 +934,12 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. } // finally, the user from the request and/or default user - userSecretSuffix := fmt.Sprintf("-%s%s", newInstance.Spec.User, crv1.UserSecretSuffix) - if secretName, password, err := createUserSecret(request, newInstance, userSecretSuffix, newInstance.Spec.User, - request.Password); err != nil { + if password, err := createUserSecret(request, newInstance, newInstance.Spec.User, request.Password); err != nil { log.Error(err) resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() return resp } else { - newInstance.Spec.UserSecretName = secretName - user := msgs.CreateClusterDetailUser{ Username: newInstance.Spec.User, Password: password, @@ -1531,43 +1521,57 @@ func getClusterParams(request *msgs.CreateClusterRequest, name string, userLabel return newInstance } -func validateSecretFrom(secretname, user, ns string) error { +// validateSecretFrom is a legacy method that looks for all of the Secrets from +// a cluster defined by "clusterName" and determines if there are bootstrap +// secrets available, i.e.: +// +// - the Postgres superuser +// - the replication user +// - a user as defined vy the "user" parameter +func validateSecretFrom(cluster *crv1.Pgcluster, secretFromClusterName string) error { ctx := context.TODO() - var err error - selector := config.LABEL_PG_CLUSTER + "=" + secretname - secrets, err := apiserver.Clientset. - CoreV1().Secrets(ns). - List(ctx, metav1.ListOptions{LabelSelector: selector}) + // grab all of the Secrets from the referenced cluster so we can determine if + // the Secrets that we are looking for are present + options := metav1.ListOptions{ + LabelSelector: fields.OneTermEqualSelector(config.LABEL_PG_CLUSTER, secretFromClusterName).String(), + } + + secrets, err := apiserver.Clientset.CoreV1().Secrets(cluster.Namespace).List(ctx, options) if err != nil { return err } - log.Debugf("secrets for %s", secretname) - pgprimaryFound := false - pgrootFound := false - pguserFound := false - - for _, s := range secrets.Items { - if s.ObjectMeta.Name == secretname+crv1.PrimarySecretSuffix { - pgprimaryFound = true - } else if s.ObjectMeta.Name == secretname+crv1.RootSecretSuffix { - pgrootFound = true - } else if s.ObjectMeta.Name == secretname+"-"+user+crv1.UserSecretSuffix { - pguserFound = true - } + // if no secrets are found, take an early exit + if len(secrets.Items) == 0 { + return fmt.Errorf("no secrets found for %q", secretFromClusterName) } - if !pgprimaryFound { - return errors.New(secretname + crv1.PrimarySecretSuffix + " not found") + + // see if all three of the secrets exist. this borrows from the legacy method + // of checking + found := map[string]bool{ + crv1.PGUserSuperuser: false, + crv1.PGUserReplication: false, + cluster.Spec.User: false, } - if !pgrootFound { - return errors.New(secretname + crv1.RootSecretSuffix + " not found") + + for _, secret := range secrets.Items { + found[crv1.PGUserSuperuser] = found[crv1.PGUserSuperuser] || + (secret.Name == crv1.UserSecretNameFromClusterName(secretFromClusterName, crv1.PGUserSuperuser)) + found[crv1.PGUserReplication] = found[crv1.PGUserReplication] || + (secret.Name == crv1.UserSecretNameFromClusterName(secretFromClusterName, crv1.PGUserReplication)) + found[cluster.Spec.User] = found[cluster.Spec.User] || + (secret.Name == crv1.UserSecretNameFromClusterName(secretFromClusterName, cluster.Spec.User)) } - if !pguserFound { - return errors.New(secretname + "-" + user + crv1.UserSecretSuffix + " not found") + + // if not all of the Secrets were found, return an error + for secretName, ok := range found { + if !ok { + return fmt.Errorf("could not find secret %q in cluster %q", secretName, secretFromClusterName) + } } - return err + return nil } func getReadyStatus(pod *v1.Pod) (string, bool) { @@ -1692,11 +1696,9 @@ func getReplicas(cluster *crv1.Pgcluster, ns string) ([]msgs.ShowClusterReplica, // password length // // returns the secertname, password as well as any errors -func createUserSecret(request *msgs.CreateClusterRequest, cluster *crv1.Pgcluster, secretNameSuffix, username, password string) (string, string, error) { +func createUserSecret(request *msgs.CreateClusterRequest, cluster *crv1.Pgcluster, username, password string) (string, error) { ctx := context.TODO() - - // the secretName is just the combination cluster name and the secretNameSuffix - secretName := fmt.Sprintf("%s%s", cluster.Spec.Name, secretNameSuffix) + secretName := crv1.UserSecretName(cluster, username) // if the secret already exists, we can perform an early exit // if there is an error, we'll ignore it @@ -1705,7 +1707,7 @@ func createUserSecret(request *msgs.CreateClusterRequest, cluster *crv1.Pgcluste Get(ctx, secretName, metav1.GetOptions{}); err == nil { log.Infof("secret exists: [%s] - skipping", secretName) - return secretName, string(secret.Data["password"][:]), nil + return string(secret.Data["password"][:]), nil } // alright, go through the hierarchy and determine if we need to set the @@ -1717,14 +1719,14 @@ func createUserSecret(request *msgs.CreateClusterRequest, cluster *crv1.Pgcluste // if the "SecretFrom" parameter is set, then load the password from a prexisting password case request.SecretFrom != "": // set up the name of the secret that we are loading the secret from - secretFromSecretName := fmt.Sprintf("%s%s", request.SecretFrom, secretNameSuffix) + secretFromSecretName := fmt.Sprintf("%s-%s-secret", request.SecretFrom, username) // now attempt to load said secret oldPassword, err := util.GetPasswordFromSecret(apiserver.Clientset, cluster.Spec.Namespace, secretFromSecretName) // if there is an error, abandon here, otherwise set the oldPassword as the // current password if err != nil { - return "", "", err + return "", err } password = oldPassword @@ -1740,7 +1742,7 @@ func createUserSecret(request *msgs.CreateClusterRequest, cluster *crv1.Pgcluste generatedPassword, err := util.GeneratePassword(passwordLength) // if the password fails to generate, return the error if err != nil { - return "", "", err + return "", err } password = generatedPassword @@ -1749,11 +1751,11 @@ func createUserSecret(request *msgs.CreateClusterRequest, cluster *crv1.Pgcluste // great, now we can create the secret! if we can't, return an error if err := util.CreateSecret(apiserver.Clientset, cluster.Spec.Name, secretName, username, password, cluster.Spec.Namespace); err != nil { - return "", "", err + return "", err } // otherwise, return the secret name, password - return secretName, password, nil + return password, nil } // UpdateCluster ... diff --git a/internal/apiserver/scheduleservice/scheduleimpl.go b/internal/apiserver/scheduleservice/scheduleimpl.go index f8c70ae059..8de3c624ea 100644 --- a/internal/apiserver/scheduleservice/scheduleimpl.go +++ b/internal/apiserver/scheduleservice/scheduleimpl.go @@ -79,7 +79,7 @@ func (s scheduleRequest) createPolicySchedule(cluster *crv1.Pgcluster, ns string } if s.Request.Secret == "" { - s.Request.Secret = cluster.Spec.PrimarySecretName + s.Request.Secret = crv1.UserSecretName(cluster, crv1.PGUserReplication) } schedule := &PgScheduleSpec{ Name: name, diff --git a/internal/apiserver/userservice/userimpl.go b/internal/apiserver/userservice/userimpl.go index cc63850ba2..1264e9b5c1 100644 --- a/internal/apiserver/userservice/userimpl.go +++ b/internal/apiserver/userservice/userimpl.go @@ -250,8 +250,7 @@ func CreateUser(request *msgs.CreateUserRequest, pgouser string) msgs.CreateUser // if this user is "managed" by the Operator, add a secret. If there is an // error, we can fall through as the next step is appending the results if request.ManagedUser { - if err := util.CreateUserSecret(apiserver.Clientset, cluster.Spec.ClusterName, result.Username, - result.Password, cluster.Spec.Namespace); err != nil { + if err := util.CreateUserSecret(apiserver.Clientset, cluster, result.Username, result.Password); err != nil { log.Error(err) result.Error = true @@ -549,16 +548,7 @@ func ShowUser(request *msgs.ShowUserRequest) msgs.ShowUserResponse { // // We ignore any errors...if the password get set, we add it. If not, we // don't - secretName := "" - - // handle special cases with user names + secrets lining up - switch result.Username { - default: - secretName = fmt.Sprintf(util.UserSecretFormat, result.ClusterName, result.Username) - case "ccp_monitoring": - secretName = util.GenerateExporterSecretName(result.ClusterName) - } - + secretName := crv1.UserSecretName(&cluster, result.Username) password, _ := util.GetPasswordFromSecret(apiserver.Clientset, pod.Namespace, secretName) if password != "" { @@ -662,7 +652,7 @@ func UpdateUser(request *msgs.UpdateUserRequest, pgouser string) msgs.UpdateUser // error in here, but do nothing with it func deleteUserSecret(cluster crv1.Pgcluster, username string) { ctx := context.TODO() - secretName := fmt.Sprintf(util.UserSecretFormat, cluster.Spec.ClusterName, username) + secretName := crv1.UserSecretName(&cluster, username) err := apiserver.Clientset.CoreV1().Secrets(cluster.Spec.Namespace). Delete(ctx, secretName, metav1.DeleteOptions{}) if err != nil { @@ -1169,13 +1159,12 @@ func updateUser(request *msgs.UpdateUserRequest, cluster *crv1.Pgcluster) msgs.U // has a "managed" account (i.e. there is a secret for this user account"), // we can now updated the value of that password in the secret if isChanged { - secretName := fmt.Sprintf(util.UserSecretFormat, cluster.Spec.ClusterName, result.Username) + secretName := crv1.UserSecretName(cluster, result.Username) // only call update user secret if the secret exists if _, err := apiserver.Clientset.CoreV1().Secrets(cluster.Namespace).Get(ctx, secretName, metav1.GetOptions{}); err == nil { // if we cannot update the user secret, only warn that we cannot do so - if err := util.UpdateUserSecret(apiserver.Clientset, cluster.Spec.ClusterName, - result.Username, result.Password, cluster.Namespace); err != nil { + if err := util.UpdateUserSecret(apiserver.Clientset, cluster, result.Username, result.Password); err != nil { log.Warn(err) } } diff --git a/internal/operator/cluster/cluster.go b/internal/operator/cluster/cluster.go index b71aac778f..0c5ddda5c2 100644 --- a/internal/operator/cluster/cluster.go +++ b/internal/operator/cluster/cluster.go @@ -699,13 +699,11 @@ func annotateBackrestSecret(clientset kubernetes.Interface, cluster *crv1.Pgclus // suffix for a given secret, as well as the user name // createUserSecret(request, newInstance, crv1.RootSecretSuffix, // crv1.PGUserSuperuser, request.PasswordSuperuser) -func createMissingUserSecret(clientset kubernetes.Interface, cluster *crv1.Pgcluster, - secretNameSuffix, username string) error { +func createMissingUserSecret(clientset kubernetes.Interface, cluster *crv1.Pgcluster, username string) error { ctx := context.TODO() - // the secretName is just the combination cluster name and the - // secretNameSuffix - secretName := cluster.Spec.Name + secretNameSuffix + // derive the secret name + secretName := crv1.UserSecretName(cluster, username) // if the secret already exists, skip it // if it returns an error other than "not found" return an error @@ -738,18 +736,17 @@ func createMissingUserSecret(clientset kubernetes.Interface, cluster *crv1.Pgclu func createMissingUserSecrets(clientset kubernetes.Interface, cluster *crv1.Pgcluster) error { // first, determine if we need to create a user secret for the postgres // superuser - if err := createMissingUserSecret(clientset, cluster, crv1.RootSecretSuffix, crv1.PGUserSuperuser); err != nil { + if err := createMissingUserSecret(clientset, cluster, crv1.PGUserSuperuser); err != nil { return err } // next, determine if we need to create a user secret for the replication user - if err := createMissingUserSecret(clientset, cluster, crv1.PrimarySecretSuffix, crv1.PGUserReplication); err != nil { + if err := createMissingUserSecret(clientset, cluster, crv1.PGUserReplication); err != nil { return err } // finally, determine if we need to create a user secret for the regular user - userSecretSuffix := fmt.Sprintf("-%s%s", cluster.Spec.User, crv1.UserSecretSuffix) - return createMissingUserSecret(clientset, cluster, userSecretSuffix, cluster.Spec.User) + return createMissingUserSecret(clientset, cluster, cluster.Spec.User) } func deleteConfigMaps(clientset kubernetes.Interface, clusterName, ns string) error { diff --git a/internal/operator/cluster/clusterlogic.go b/internal/operator/cluster/clusterlogic.go index 2eca688f6a..f91e45b365 100644 --- a/internal/operator/cluster/clusterlogic.go +++ b/internal/operator/cluster/clusterlogic.go @@ -315,9 +315,9 @@ func getClusterDeploymentFields(clientset kubernetes.Interface, DataPathOverride: cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY], Database: cl.Spec.Database, SecurityContext: operator.GetPodSecurityContext(supplementalGroups), - RootSecretName: cl.Spec.RootSecretName, - PrimarySecretName: cl.Spec.PrimarySecretName, - UserSecretName: cl.Spec.UserSecretName, + RootSecretName: crv1.UserSecretName(cl, crv1.PGUserSuperuser), + PrimarySecretName: crv1.UserSecretName(cl, crv1.PGUserReplication), + UserSecretName: crv1.UserSecretName(cl, cl.Spec.User), NodeSelector: operator.GetAffinity(cl.Spec.UserLabels["NodeLabelKey"], cl.Spec.UserLabels["NodeLabelValue"], "In"), PodAntiAffinity: operator.GetPodAntiAffinity(cl, crv1.PodAntiAffinityDeploymentDefault, cl.Spec.PodAntiAffinity.Default), ContainerResources: operator.GetResourcesJSON(cl.Spec.Resources, cl.Spec.Limits), @@ -473,9 +473,9 @@ func scaleReplicaCreateDeployment(clientset kubernetes.Interface, PodAnnotations: operator.GetAnnotations(cluster, crv1.ClusterAnnotationPostgres), PodLabels: operator.GetLabelsFromMap(cluster.Spec.UserLabels), SecurityContext: operator.GetPodSecurityContext(supplementalGroups), - RootSecretName: cluster.Spec.RootSecretName, - PrimarySecretName: cluster.Spec.PrimarySecretName, - UserSecretName: cluster.Spec.UserSecretName, + RootSecretName: crv1.UserSecretName(cluster, crv1.PGUserSuperuser), + PrimarySecretName: crv1.UserSecretName(cluster, crv1.PGUserReplication), + UserSecretName: crv1.UserSecretName(cluster, cluster.Spec.User), ContainerResources: operator.GetResourcesJSON(cluster.Spec.Resources, cluster.Spec.Limits), NodeSelector: operator.GetAffinity(replica.Spec.UserLabels["NodeLabelKey"], replica.Spec.UserLabels["NodeLabelValue"], "In"), PodAntiAffinity: operator.GetPodAntiAffinity(cluster, crv1.PodAntiAffinityDeploymentDefault, cluster.Spec.PodAntiAffinity.Default), diff --git a/internal/util/secrets.go b/internal/util/secrets.go index c8509e332f..be8c2f4288 100644 --- a/internal/util/secrets.go +++ b/internal/util/secrets.go @@ -18,7 +18,6 @@ package util import ( "context" "crypto/rand" - "fmt" "math/big" "strconv" "strings" @@ -32,10 +31,6 @@ import ( "k8s.io/client-go/kubernetes" ) -// UserSecretFormat follows the pattern of how the user information is stored, -// which is "--secret" -const UserSecretFormat = "%s-%s" + crv1.UserSecretSuffix - // The following constants are used as a part of password generation. For more // information on these selections, please consulting the ASCII man page // (`man ascii`) @@ -141,10 +136,10 @@ func IsPostgreSQLUserSystemAccount(username string) bool { } // CreateUserSecret will create a new secret holding a user credential -func CreateUserSecret(clientset kubernetes.Interface, clustername, username, password, namespace string) error { - secretName := fmt.Sprintf(UserSecretFormat, clustername, username) +func CreateUserSecret(clientset kubernetes.Interface, cluster *crv1.Pgcluster, username, password string) error { + secretName := crv1.UserSecretName(cluster, username) - if err := CreateSecret(clientset, clustername, secretName, username, password, namespace); err != nil { + if err := CreateSecret(clientset, cluster.Name, secretName, username, password, cluster.Namespace); err != nil { log.Error(err) return err } @@ -157,12 +152,12 @@ func CreateUserSecret(clientset kubernetes.Interface, clustername, username, pas // // 1. If the Secret exists, it updates the value of the Secret // 2. If the Secret does not exist, it creates the secret -func UpdateUserSecret(clientset kubernetes.Interface, clustername, username, password, namespace string) error { +func UpdateUserSecret(clientset kubernetes.Interface, cluster *crv1.Pgcluster, username, password string) error { ctx := context.TODO() - secretName := fmt.Sprintf(UserSecretFormat, clustername, username) + secretName := crv1.UserSecretName(cluster, username) // see if the secret already exists - secret, err := clientset.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{}) + secret, err := clientset.CoreV1().Secrets(cluster.Namespace).Get(ctx, secretName, metav1.GetOptions{}) // if this returns an error and it's not the "not found" error, return // However, if it is the "not found" error, treat this as creating the user // secret @@ -171,7 +166,7 @@ func UpdateUserSecret(clientset kubernetes.Interface, clustername, username, pas return err } - return CreateUserSecret(clientset, clustername, username, password, namespace) + return CreateUserSecret(clientset, cluster, username, password) } // update the value of "password" diff --git a/pkg/apis/crunchydata.com/v1/cluster.go b/pkg/apis/crunchydata.com/v1/cluster.go index c487bc81b8..d89e10f6c5 100644 --- a/pkg/apis/crunchydata.com/v1/cluster.go +++ b/pkg/apis/crunchydata.com/v1/cluster.go @@ -107,9 +107,6 @@ type PgclusterSpec struct { User string `json:"user"` Database string `json:"database"` Replicas string `json:"replicas"` - UserSecretName string `json:"usersecretname"` - RootSecretName string `json:"rootsecretname"` - PrimarySecretName string `json:"primarysecretname"` Status string `json:"status"` CustomConfig string `json:"customconfig"` UserLabels map[string]string `json:"userlabels"` @@ -348,3 +345,23 @@ func (p PodAntiAffinityType) Validate() error { return fmt.Errorf("Invalid pod anti-affinity type. Valid values are '%s', '%s' or '%s'", PodAntiAffinityRequired, PodAntiAffinityPreffered, PodAntiAffinityDisabled) } + +// UserSecretName returns the name of a Kubernetes Secret representing the user. +// Delegates to UserSecretNameFromClusterName. This is the preferred method +// given there is less thinking for the caller to do, but there are some (one?) +// cases where UserSecretNameFromClusterName needs to be called as that cluster +// object is unavailable +func UserSecretName(cluster *Pgcluster, username string) string { + return UserSecretNameFromClusterName(cluster.Name, username) +} + +// UserSecretNameFromClusterName returns the name of a Kubernetes Secret +// representing a user. +func UserSecretNameFromClusterName(clusterName, username string) string { + switch username { + default: // standard format + return fmt.Sprintf("%s-%s-secret", clusterName, username) + case PGUserMonitor: + return fmt.Sprintf("%s-exporter-secret", clusterName) + } +} diff --git a/pkg/apis/crunchydata.com/v1/cluster_test.go b/pkg/apis/crunchydata.com/v1/cluster_test.go new file mode 100644 index 0000000000..c2f6c70bb3 --- /dev/null +++ b/pkg/apis/crunchydata.com/v1/cluster_test.go @@ -0,0 +1,75 @@ +package v1 + +/* + Copyright 2020 Crunchy Data Solutions, Inc. + 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. +*/ + +import ( + "fmt" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestUserSecretName(t *testing.T) { + cluster := &Pgcluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "second-pick", + }, + Spec: PgclusterSpec{ + ClusterName: "second-pick", + User: "puppy", + }, + } + + t.Run(PGUserMonitor, func(t *testing.T) { + expected := fmt.Sprintf("%s-%s-secret", cluster.Name, "exporter") + actual := UserSecretName(cluster, PGUserMonitor) + if expected != actual { + t.Fatalf("expected %q, got %q", expected, actual) + } + }) + + t.Run("any other user", func(t *testing.T) { + for _, user := range []string{PGUserSuperuser, PGUserReplication, cluster.Spec.User} { + expected := fmt.Sprintf("%s-%s-secret", cluster.Name, user) + actual := UserSecretName(cluster, user) + if expected != actual { + t.Fatalf("expected %q, got %q", expected, actual) + } + } + }) +} + +func TestUserSecretNameFromClusterName(t *testing.T) { + clusterName := "second-pick" + + t.Run(PGUserMonitor, func(t *testing.T) { + expected := fmt.Sprintf("%s-%s-secret", clusterName, "exporter") + actual := UserSecretNameFromClusterName(clusterName, PGUserMonitor) + if expected != actual { + t.Fatalf("expected %q, got %q", expected, actual) + } + }) + + t.Run("any other user", func(t *testing.T) { + for _, user := range []string{PGUserSuperuser, PGUserReplication, "puppy"} { + expected := fmt.Sprintf("%s-%s-secret", clusterName, user) + actual := UserSecretNameFromClusterName(clusterName, user) + if expected != actual { + t.Fatalf("expected %q, got %q", expected, actual) + } + } + }) +} diff --git a/pkg/apis/crunchydata.com/v1/common.go b/pkg/apis/crunchydata.com/v1/common.go index fcd2238f36..33818edf72 100644 --- a/pkg/apis/crunchydata.com/v1/common.go +++ b/pkg/apis/crunchydata.com/v1/common.go @@ -22,15 +22,6 @@ import ( log "github.com/sirupsen/logrus" ) -// RootSecretSuffix ... -const RootSecretSuffix = "-postgres-secret" - -// UserSecretSuffix ... -const UserSecretSuffix = "-secret" - -// PrimarySecretSuffix ... -const PrimarySecretSuffix = "-primaryuser-secret" - // StorageExisting ... const StorageExisting = "existing" From c91015cfe28ea870fae1699c676adeb977b36e4d Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 27 Dec 2020 21:41:46 -0500 Subject: [PATCH 082/276] Remove superfluous "pgo-backrest" label on pgcluster This is not used anymore as pgBackRest is always enabled for a PostgreSQL cluster, and even if it were togglable, this would need to be a CRD attribute. This also removes the "ArchiveMode" configuration parameter, which has not been used for a long time. --- conf/postgres-operator/pgo.yaml | 1 - docs/content/custom-resources/_index.md | 2 -- examples/create-by-resource/fromcrd.json | 4 +-- .../create-cluster/templates/pgcluster.yaml | 1 - examples/kustomize/createcluster/README.md | 8 ++--- .../createcluster/base/pgcluster.yaml | 1 - .../roles/pgo-operator/templates/pgo.yaml.j2 | 1 - .../apiserver/backrestservice/backrestimpl.go | 13 ------- .../apiserver/clusterservice/clusterimpl.go | 4 --- internal/operator/cluster/clusterlogic.go | 14 ++------ internal/operator/cluster/upgrade.go | 4 --- internal/operator/clusterutilities.go | 36 +++++++++---------- 12 files changed, 23 insertions(+), 66 deletions(-) diff --git a/conf/postgres-operator/pgo.yaml b/conf/postgres-operator/pgo.yaml index 622fc268f8..0cb8bfadc1 100644 --- a/conf/postgres-operator/pgo.yaml +++ b/conf/postgres-operator/pgo.yaml @@ -11,7 +11,6 @@ Cluster: PasswordAgeDays: 0 PasswordLength: 24 Replicas: 0 - ArchiveMode: false ServiceType: ClusterIP BackrestPort: 2022 BackrestS3Bucket: diff --git a/docs/content/custom-resources/_index.md b/docs/content/custom-resources/_index.md index a9f5879c4f..b5d58308c0 100644 --- a/docs/content/custom-resources/_index.md +++ b/docs/content/custom-resources/_index.md @@ -83,7 +83,6 @@ metadata: name: ${pgo_cluster_name} pg-cluster: ${pgo_cluster_name} pg-pod-anti-affinity: "" - pgo-backrest: "true" pgo-version: {{< param operatorVersion >}} pgouser: admin name: ${pgo_cluster_name} @@ -301,7 +300,6 @@ metadata: name: ${pgo_cluster_name} pg-cluster: ${pgo_cluster_name} pg-pod-anti-affinity: "" - pgo-backrest: "true" pgo-version: {{< param operatorVersion >}} pgouser: admin name: ${pgo_cluster_name} diff --git a/examples/create-by-resource/fromcrd.json b/examples/create-by-resource/fromcrd.json index 8713deb719..30384d2316 100644 --- a/examples/create-by-resource/fromcrd.json +++ b/examples/create-by-resource/fromcrd.json @@ -15,7 +15,6 @@ "name": "fromcrd", "pg-cluster": "fromcrd", "pg-pod-anti-affinity": "", - "pgo-backrest": "true", "pgo-version": "4.5.1", "pgouser": "pgoadmin", "primary": "true" @@ -85,8 +84,7 @@ "crunchy-postgres-exporter": "false", "pg-pod-anti-affinity": "", "pgo-version": "4.5.1", - "pgouser": "pgoadmin", - "pgo-backrest": "true" + "pgouser": "pgoadmin" } } } diff --git a/examples/helm/create-cluster/templates/pgcluster.yaml b/examples/helm/create-cluster/templates/pgcluster.yaml index 65f12d6460..3378f37fd9 100644 --- a/examples/helm/create-cluster/templates/pgcluster.yaml +++ b/examples/helm/create-cluster/templates/pgcluster.yaml @@ -12,7 +12,6 @@ metadata: name: {{ .Values.pgclustername }} pg-cluster: {{ .Values.pgclustername }} pg-pod-anti-affinity: "" - pgo-backrest: "true" pgo-version: 4.5.1 pgouser: admin name: {{ .Values.pgclustername }} diff --git a/examples/kustomize/createcluster/README.md b/examples/kustomize/createcluster/README.md index f21cae2f90..ddca1fd70a 100644 --- a/examples/kustomize/createcluster/README.md +++ b/examples/kustomize/createcluster/README.md @@ -50,7 +50,7 @@ cluster : hippo (crunchy-postgres-ha:centos7-12.5-4.5.1) deployment : hippo deployment : hippo-backrest-shared-repo service : hippo - ClusterIP (10.0.56.86) - Ports (2022/TCP, 5432/TCP) - labels : pg-pod-anti-affinity= pgo-backrest=true pgo-version=4.5.1 crunchy-postgres-exporter=false name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata autofail=true crunchy-pgbadger=false + labels : pg-pod-anti-affinity= pgo-version=4.5.1 crunchy-postgres-exporter=false name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata autofail=true crunchy-pgbadger=false ``` Feel free to run other pgo cli commands on the hippo cluster @@ -87,7 +87,7 @@ cluster : dev-hippo (crunchy-postgres-ha:centos7-12.5-4.5.1) deployment : dev-hippo-pgbouncer service : dev-hippo - ClusterIP (10.0.62.87) - Ports (2022/TCP, 5432/TCP) service : dev-hippo-pgbouncer - ClusterIP (10.0.48.120) - Ports (5432/TCP) - labels : crunchy-pgha-scope=dev-hippo crunchy-postgres-exporter=false name=dev-hippo pg-cluster=dev-hippo pg-pod-anti-affinity= pgo-backrest=true vendor=crunchydata autofail=true crunchy-pgbadger=false deployment-name=dev-hippo environment=development pgo-version=4.5.1 pgouser=admin + labels : crunchy-pgha-scope=dev-hippo crunchy-postgres-exporter=false name=dev-hippo pg-cluster=dev-hippo pg-pod-anti-affinity= vendor=crunchydata autofail=true crunchy-pgbadger=false deployment-name=dev-hippo environment=development pgo-version=4.5.1 pgouser=admin ``` #### staging The staging overlay will deploy a crunchy postgreSQL cluster with 2 replica's with annotations added @@ -128,7 +128,7 @@ cluster : staging-hippo (crunchy-postgres-ha:centos7-12.5-4.5.1) service : staging-hippo-replica - ClusterIP (10.0.56.57) - Ports (2022/TCP, 5432/TCP) pgreplica : staging-hippo-lnxw pgreplica : staging-hippo-rpl1 - labels : deployment-name=staging-hippo environment=staging name=staging-hippo pg-pod-anti-affinity= crunchy-postgres-exporter=false crunchy-pgbadger=false crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-backrest=true pgo-version=4.5.1 pgouser=admin vendor=crunchydata autofail=true + labels : deployment-name=staging-hippo environment=staging name=staging-hippo pg-pod-anti-affinity= crunchy-postgres-exporter=false crunchy-pgbadger=false crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.5.1 pgouser=admin vendor=crunchydata autofail=true ``` #### production @@ -165,7 +165,7 @@ cluster : prod-hippo (crunchy-postgres-ha:centos7-12.5-4.5.1) service : prod-hippo - ClusterIP (10.0.56.18) - Ports (2022/TCP, 5432/TCP) service : prod-hippo-replica - ClusterIP (10.0.56.101) - Ports (2022/TCP, 5432/TCP) pgreplica : prod-hippo-flty - labels : pgo-backrest=true pgo-version=4.5.1 crunchy-pgbadger=false crunchy-postgres-exporter=false deployment-name=prod-hippo environment=production pg-cluster=prod-hippo pg-pod-anti-affinity= autofail=true crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.5.1 crunchy-pgbadger=false crunchy-postgres-exporter=false deployment-name=prod-hippo environment=production pg-cluster=prod-hippo pg-pod-anti-affinity= autofail=true crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata ``` ### Delete the clusters To delete the clusters run the following pgo cli commands diff --git a/examples/kustomize/createcluster/base/pgcluster.yaml b/examples/kustomize/createcluster/base/pgcluster.yaml index 975a7ddf5d..6dbcd21517 100644 --- a/examples/kustomize/createcluster/base/pgcluster.yaml +++ b/examples/kustomize/createcluster/base/pgcluster.yaml @@ -12,7 +12,6 @@ metadata: name: hippo pg-cluster: hippo pg-pod-anti-affinity: "" - pgo-backrest: "true" pgo-version: 4.5.1 pgouser: admin name: hippo diff --git a/installers/ansible/roles/pgo-operator/templates/pgo.yaml.j2 b/installers/ansible/roles/pgo-operator/templates/pgo.yaml.j2 index e87a245bed..2c8f272bde 100644 --- a/installers/ansible/roles/pgo-operator/templates/pgo.yaml.j2 +++ b/installers/ansible/roles/pgo-operator/templates/pgo.yaml.j2 @@ -18,7 +18,6 @@ Cluster: PasswordAgeDays: {{ db_password_age_days }} PasswordLength: {{ db_password_length }} Replicas: {{ db_replicas }} - ArchiveMode: {{ archive_mode }} ServiceType: {{ service_type }} DisableReplicaStartFailReinit: {{ disable_replica_start_fail_reinit }} PodAntiAffinity: {{ pod_anti_affinity }} diff --git a/internal/apiserver/backrestservice/backrestimpl.go b/internal/apiserver/backrestservice/backrestimpl.go index 6f1aee538a..1d545e387e 100644 --- a/internal/apiserver/backrestservice/backrestimpl.go +++ b/internal/apiserver/backrestservice/backrestimpl.go @@ -150,12 +150,6 @@ func CreateBackup(request *msgs.CreateBackrestBackupRequest, ns, pgouser string) return resp } - if cluster.Labels[config.LABEL_BACKREST] != "true" { - resp.Status.Code = msgs.Error - resp.Status.Msg = clusterName + " does not have pgbackrest enabled" - return resp - } - err = util.ValidateBackrestStorageTypeOnBackupRestore(request.BackrestStorageType, cluster.Spec.UserLabels[config.LABEL_BACKREST_STORAGE_TYPE], false) if err != nil { @@ -551,13 +545,6 @@ func Restore(request *msgs.RestoreRequest, ns, pgouser string) msgs.RestoreRespo return resp } - // verify that the cluster we are restoring from has backrest enabled - if cluster.Labels[config.LABEL_BACKREST] != "true" { - resp.Status.Code = msgs.Error - resp.Status.Msg = "can't restore, cluster restoring from does not have backrest enabled" - return resp - } - // Return an error if any clusters identified for the restore are in standby mode. Restoring // from a standby cluster is not allowed since the cluster is following a remote primary, // which itself is responsible for performing any restores as required for the cluster. diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index 43106d7dba..ee8b01e0d6 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -1502,10 +1502,6 @@ func getClusterParams(request *msgs.CreateClusterRequest, name string, userLabel labels[config.LABEL_BADGER] = "true" } - // pgBackRest is always set to true. This is here due to a time where - // pgBackRest was not the only way - labels[config.LABEL_BACKREST] = "true" - newInstance := &crv1.Pgcluster{ ObjectMeta: metav1.ObjectMeta{ Name: name, diff --git a/internal/operator/cluster/clusterlogic.go b/internal/operator/cluster/clusterlogic.go index f91e45b365..e527df2cd4 100644 --- a/internal/operator/cluster/clusterlogic.go +++ b/internal/operator/cluster/clusterlogic.go @@ -326,7 +326,7 @@ func getClusterDeploymentFields(clientset kubernetes.Interface, BadgerAddon: operator.GetBadgerAddon(clientset, namespace, cl, cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY]), PgmonitorEnvVars: operator.GetPgmonitorEnvVars(cl), ScopeLabel: config.LABEL_PGHA_SCOPE, - PgbackrestEnvVars: operator.GetPgbackrestEnvVars(cl, cl.Labels[config.LABEL_BACKREST], cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY], + PgbackrestEnvVars: operator.GetPgbackrestEnvVars(cl, cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY], cl.Spec.Port, cl.Spec.UserLabels[config.LABEL_BACKREST_STORAGE_TYPE]), PgbackrestS3EnvVars: operator.GetPgbackrestS3EnvVars(*cl, clientset, namespace), ReplicaReinitOnStartFail: !operator.Pgo.Cluster.DisableReplicaStartFailReinit, @@ -421,15 +421,6 @@ func scaleReplicaCreateDeployment(clientset kubernetes.Interface, cluster.Spec.UserLabels["name"] = serviceName cluster.Spec.UserLabels[config.LABEL_PG_CLUSTER] = replica.Spec.ClusterName - archiveMode := "off" - if cluster.Spec.UserLabels[config.LABEL_ARCHIVE] == "true" { - archiveMode = "on" - } - if cluster.Labels[config.LABEL_BACKREST] == "true" { - // backrest requires archive mode be set to on - archiveMode = "on" - } - image := cluster.Spec.CCPImage // check for --ccp-image-tag at the command line @@ -466,7 +457,6 @@ func scaleReplicaCreateDeployment(clientset kubernetes.Interface, PVCName: dataVolume.InlineVolumeSource(), Database: cluster.Spec.Database, DataPathOverride: replica.Spec.Name, - ArchiveMode: archiveMode, Replicas: "1", ConfVolume: operator.GetConfVolume(clientset, cluster, namespace), DeploymentLabels: operator.GetLabelsFromMap(cluster.Spec.UserLabels), @@ -482,7 +472,7 @@ func scaleReplicaCreateDeployment(clientset kubernetes.Interface, ExporterAddon: operator.GetExporterAddon(cluster.Spec), BadgerAddon: operator.GetBadgerAddon(clientset, namespace, cluster, replica.Spec.Name), ScopeLabel: config.LABEL_PGHA_SCOPE, - PgbackrestEnvVars: operator.GetPgbackrestEnvVars(cluster, cluster.Labels[config.LABEL_BACKREST], replica.Spec.Name, + PgbackrestEnvVars: operator.GetPgbackrestEnvVars(cluster, replica.Spec.Name, cluster.Spec.Port, cluster.Spec.UserLabels[config.LABEL_BACKREST_STORAGE_TYPE]), PgbackrestS3EnvVars: operator.GetPgbackrestS3EnvVars(*cluster, clientset, namespace), ReplicaReinitOnStartFail: !operator.Pgo.Cluster.DisableReplicaStartFailReinit, diff --git a/internal/operator/cluster/upgrade.go b/internal/operator/cluster/upgrade.go index 4bb6d63a53..0b12c5c4ff 100644 --- a/internal/operator/cluster/upgrade.go +++ b/internal/operator/cluster/upgrade.go @@ -540,10 +540,6 @@ func preparePgclusterForUpgrade(pgcluster *crv1.Pgcluster, parameters map[string pgcluster.Spec.PGBadgerPort = operator.Pgo.Cluster.PGBadgerPort } - // ensure that the pgo-backrest label is set to 'true' since pgbackrest is required for normal - // cluster operations in this version of the Postgres Operator - pgcluster.ObjectMeta.Labels[config.LABEL_BACKREST] = "true" - // added in 4.2 and copied from configuration in 4.4 if pgcluster.Spec.BackrestS3Bucket == "" { pgcluster.Spec.BackrestS3Bucket = operator.Pgo.Cluster.BackrestS3Bucket diff --git a/internal/operator/clusterutilities.go b/internal/operator/clusterutilities.go index 163850861e..3281a43c7e 100644 --- a/internal/operator/clusterutilities.go +++ b/internal/operator/clusterutilities.go @@ -161,7 +161,6 @@ type DeploymentTemplateFields struct { PodAnnotations string PodLabels string DataPathOverride string - ArchiveMode string PVCName string RootSecretName string UserSecretName string @@ -277,27 +276,24 @@ func GetAnnotations(cluster *crv1.Pgcluster, annotationType crv1.ClusterAnnotati } // consolidate with cluster.GetPgbackrestEnvVars -func GetPgbackrestEnvVars(cluster *crv1.Pgcluster, backrestEnabled, depName, port, storageType string) string { - if backrestEnabled == "true" { - fields := PgbackrestEnvVarsTemplateFields{ - PgbackrestStanza: "db", - PgbackrestRepo1Host: cluster.Name + "-backrest-shared-repo", - PgbackrestRepo1Path: util.GetPGBackRestRepoPath(*cluster), - PgbackrestDBPath: "/pgdata/" + depName, - PgbackrestPGPort: port, - PgbackrestRepo1Type: GetRepoType(storageType), - PgbackrestLocalAndS3Storage: IsLocalAndS3Storage(storageType), - } +func GetPgbackrestEnvVars(cluster *crv1.Pgcluster, depName, port, storageType string) string { + fields := PgbackrestEnvVarsTemplateFields{ + PgbackrestStanza: "db", + PgbackrestRepo1Host: cluster.Name + "-backrest-shared-repo", + PgbackrestRepo1Path: util.GetPGBackRestRepoPath(*cluster), + PgbackrestDBPath: "/pgdata/" + depName, + PgbackrestPGPort: port, + PgbackrestRepo1Type: GetRepoType(storageType), + PgbackrestLocalAndS3Storage: IsLocalAndS3Storage(storageType), + } - var doc bytes.Buffer - err := config.PgbackrestEnvVarsTemplate.Execute(&doc, fields) - if err != nil { - log.Error(err.Error()) - return "" - } - return doc.String() + doc := bytes.Buffer{} + if err := config.PgbackrestEnvVarsTemplate.Execute(&doc, fields); err != nil { + log.Error(err.Error()) + return "" } - return "" + + return doc.String() } // GetPgbackrestBootstrapEnvVars returns a string containing the pgBackRest environment variables From 41eff90f1cebe230f8003241a8ef3fe40cc2fa88 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 27 Dec 2020 22:30:33 -0500 Subject: [PATCH 083/276] Simplify the custom resource examples for pgcluster There are many attributes that are not actually required to be set when creating a new PostgreSQL cluster. This simplifies the examples to only use the attributes that are (mostly) required to be set. --- docs/content/custom-resources/_index.md | 50 ++----------------- examples/create-by-resource/fromcrd.json | 25 +--------- .../create-cluster/templates/pgcluster.yaml | 29 +---------- .../createcluster/base/pgcluster.yaml | 36 +------------ 4 files changed, 8 insertions(+), 132 deletions(-) diff --git a/docs/content/custom-resources/_index.md b/docs/content/custom-resources/_index.md index b5d58308c0..eb3f7cc21c 100644 --- a/docs/content/custom-resources/_index.md +++ b/docs/content/custom-resources/_index.md @@ -112,30 +112,16 @@ spec: storageclass: "" storagetype: create supplementalgroups: "" - annotations: - backrestLimits: {} - backrestRepoPath: "" - backrestResources: - memory: 48Mi - backrestS3Bucket: "" - backrestS3Endpoint: "" - backrestS3Region: "" - backrestS3URIStyle: "" - backrestS3VerifyTLS: "" + annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: registry.developers.crunchydata.com/crunchydata ccpimagetag: {{< param centosBase >}}-{{< param postgresVersion >}}-{{< param operatorVersion >}} clustername: ${pgo_cluster_name} - customconfig: "" database: ${pgo_cluster_name} - exporter: false exporterport: "9187" limits: {} name: ${pgo_cluster_name} namespace: ${cluster_namespace} - pgBouncer: - limits: {} - replicas: 0 pgDataSource: restoreFrom: "" restoreOpts: "" @@ -145,21 +131,10 @@ spec: default: preferred pgBackRest: preferred pgBouncer: preferred - policies: "" port: "5432" - replicas: "0" - shutdown: false - standby: false - tablespaceMounts: {} - tls: - caSecret: "" - replicationTLSSecret: "" - tlsSecret: "" - tlsOnly: false tolerations: [] user: hippo userlabels: - pg-pod-anti-affinity: "" pgo-version: {{< param operatorVersion >}} EOF @@ -329,11 +304,7 @@ spec: storageclass: "" storagetype: dynamic supplementalgroups: "" - annotations: - backrestLimits: {} - backrestRepoPath: "" - backrestResources: - memory: 48Mi + annotations: {} backrestS3Bucket: ${backrest_s3_bucket} backrestS3Endpoint: ${backrest_s3_endpoint} backrestS3Region: ${backrest_s3_region} @@ -343,16 +314,11 @@ spec: ccpimageprefix: registry.developers.crunchydata.com/crunchydata ccpimagetag: {{< param centosBase >}}-{{< param postgresVersion >}}-{{< param operatorVersion >}} clustername: ${pgo_cluster_name} - customconfig: "" database: ${pgo_cluster_name} - exporter: false exporterport: "9187" limits: {} name: ${pgo_cluster_name} namespace: ${cluster_namespace} - pgBouncer: - limits: {} - replicas: 0 pgDataSource: restoreFrom: "" restoreOpts: "" @@ -362,22 +328,11 @@ spec: default: preferred pgBackRest: preferred pgBouncer: preferred - policies: "" port: "5432" - replicas: "0" - shutdown: false - standby: false - tablespaceMounts: {} - tls: - caSecret: "" - replicationTLSSecret: "" - tlsSecret: "" - tlsOnly: false tolerations: [] user: hippo userlabels: backrest-storage-type: "s3" - pg-pod-anti-affinity: "" pgo-version: {{< param operatorVersion >}} EOF @@ -469,6 +424,7 @@ spec: storageclass: "" storagetype: create supplementalgroups: "" + tolerations: [] userlabels: NodeLabelKey: "" NodeLabelValue: "" diff --git a/examples/create-by-resource/fromcrd.json b/examples/create-by-resource/fromcrd.json index 30384d2316..49e4665fe7 100644 --- a/examples/create-by-resource/fromcrd.json +++ b/examples/create-by-resource/fromcrd.json @@ -9,15 +9,12 @@ "autofail": "true", "crunchy-pgbadger": "false", "crunchy-pgha-scope": "fromcrd", - "crunchy-postgres-exporter": "false", - "current-primary": "fromcrd", "deployment-name": "fromcrd", "name": "fromcrd", "pg-cluster": "fromcrd", "pg-pod-anti-affinity": "", "pgo-version": "4.5.1", - "pgouser": "pgoadmin", - "primary": "true" + "pgouser": "pgoadmin" }, "name": "fromcrd", "namespace": "pgouser1" @@ -50,41 +47,23 @@ "storagetype": "dynamic", "supplementalgroups": "" }, - "backrestResources": {}, "ccpimage": "crunchy-postgres-ha", "ccpimagetag": "centos7-12.5-4.5.1", "clustername": "fromcrd", - "customconfig": "", "database": "userdb", "exporterport": "9187", "name": "fromcrd", "namespace": "pgouser1", - "pgBouncer": { - "replicas": 0, - "resources": {} - }, "pgbadgerport": "10000", "podPodAntiAffinity": { "default": "preferred", "pgBackRest": "preferred", "pgBouncer": "preferred" }, - "policies": "", "port": "5432", - "replicas": "0", - "secretfrom": "", - "shutdown": false, - "standby": false, - "status": "", - "syncReplication": null, - "tablespaceMounts": {}, - "tls": {}, "user": "testuser", "userlabels": { - "crunchy-postgres-exporter": "false", - "pg-pod-anti-affinity": "", - "pgo-version": "4.5.1", - "pgouser": "pgoadmin" + "pgo-version": "4.5.1" } } } diff --git a/examples/helm/create-cluster/templates/pgcluster.yaml b/examples/helm/create-cluster/templates/pgcluster.yaml index 3378f37fd9..1a5e99617d 100644 --- a/examples/helm/create-cluster/templates/pgcluster.yaml +++ b/examples/helm/create-cluster/templates/pgcluster.yaml @@ -7,7 +7,6 @@ metadata: autofail: "true" crunchy-pgbadger: "false" crunchy-pgha-scope: {{ .Values.pgclustername }} - crunchy-postgres-exporter: "false" deployment-name: {{ .Values.pgclustername }} name: {{ .Values.pgclustername }} pg-cluster: {{ .Values.pgclustername }} @@ -41,29 +40,17 @@ spec: storageclass: "" storagetype: dynamic supplementalgroups: "" - annotations: - backrestLimits: {} - backrestRepoPath: "" - backrestResources: - memory: 48Mi - backrestS3Bucket: "" - backrestS3Endpoint: "" - backrestS3Region: "" - backrestS3URIStyle: "" - backrestS3VerifyTLS: "" + annotations: {} ccpimage: {{ .Values.ccpimage }} ccpimageprefix: {{ .Values.ccpimageprefix }} ccpimagetag: {{ .Values.ccpimagetag }} clustername: {{ .Values.pgclustername }} - customconfig: "" database: {{ .Values.pgclustername }} + exporter: false exporterport: "9187" limits: {} name: {{ .Values.pgclustername }} namespace: {{ .Values.namespace }} - pgBouncer: - limits: {} - replicas: 0 pgDataSource: restoreFrom: "" restoreOpts: "" @@ -73,19 +60,7 @@ spec: default: preferred pgBackRest: preferred pgBouncer: preferred - policies: "" port: "5432" - replicas: "0" - shutdown: false - standby: false - tablespaceMounts: {} - tls: - caSecret: "" - replicationTLSSecret: "" - tlsSecret: "" - tlsOnly: false user: hippo userlabels: - crunchy-postgres-exporter: "false" - pg-pod-anti-affinity: "" pgo-version: {{ .Values.pgoversion }} diff --git a/examples/kustomize/createcluster/base/pgcluster.yaml b/examples/kustomize/createcluster/base/pgcluster.yaml index 6dbcd21517..ed7c27622d 100644 --- a/examples/kustomize/createcluster/base/pgcluster.yaml +++ b/examples/kustomize/createcluster/base/pgcluster.yaml @@ -7,7 +7,6 @@ metadata: autofail: "true" crunchy-pgbadger: "false" crunchy-pgha-scope: hippo - crunchy-postgres-exporter: "false" deployment-name: hippo name: hippo pg-cluster: hippo @@ -41,24 +40,7 @@ spec: storageclass: "" storagetype: dynamic supplementalgroups: "" - annotations: - global: - favorite: "" - backrest: - chair: "" - pgBouncer: - pool: "" - postgres: - elephant: "" - backrestLimits: {} - backrestRepoPath: "" - backrestResources: - memory: 48Mi - backrestS3Bucket: "" - backrestS3Endpoint: "" - backrestS3Region: "" - backrestS3URIStyle: "" - backrestS3VerifyTLS: "" + annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: registry.developers.crunchydata.com/crunchydata ccpimagetag: centos7-12.5-4.5.1 @@ -69,11 +51,6 @@ spec: limits: {} name: hippo namespace: pgo - pgBouncer: - limits: {} - replicas: 0 - resources: - memory: "0" pgDataSource: restoreFrom: "" restoreOpts: "" @@ -85,17 +62,6 @@ spec: pgBouncer: preferred policies: "" port: "5432" - replicas: "0" - shutdown: false - standby: false - tablespaceMounts: {} - tls: - caSecret: "" - replicationTLSSecret: "" - tlsSecret: "" - tlsOnly: false user: hippo userlabels: - crunchy-postgres-exporter: "false" - pg-pod-anti-affinity: "" pgo-version: 4.5.1 From dccdb9dd82bcd7e00378cdaa696ac0e8be271bee Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 27 Dec 2020 23:05:08 -0500 Subject: [PATCH 084/276] Add documentation for TLS-enabled clusters with CR workflow This adds an example for how to create a TLS-enabled PostgreSQL cluster via a custom resource. --- docs/content/custom-resources/_index.md | 126 ++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/docs/content/custom-resources/_index.md b/docs/content/custom-resources/_index.md index eb3f7cc21c..f327e17667 100644 --- a/docs/content/custom-resources/_index.md +++ b/docs/content/custom-resources/_index.md @@ -339,6 +339,132 @@ EOF kubectl apply -f "${pgo_cluster_name}-pgcluster.yaml" ``` +### Create a PostgreSQL Cluster with TLS + +There are three items that are required to enable TLS in your PostgreSQL clusters: + +- A CA certificate +- A TLS private key +- A TLS certificate + +It is possible [create a PostgreSQL cluster with TLS]({{< relref "tutorial/tls.md" >}}) using a custom resource workflow with the prerequisite of ensuring the above three items are created. + +For a detailed explanation for how TLS works with the PostgreSQL Operator, please see the [TLS tutorial]({{< relref "tutorial/tls.md" >}}). + +#### Step 1: Create TLS Secrets + +There are two Secrets that need to be created: + +1. A Secret containing the certificate authority (CA). You may only need to create this Secret once, as a CA certificate can be shared amongst your clusters. +2. A Secret that contains the TLS private key & certificate. + +This assumes that you have already [generated your TLS certificates](https://www.postgresql.org/docs/current/ssl-tcp.html#SSL-CERTIFICATE-CREATION) where the CA is named `ca.crt` and the server key and certificate are named `server.key` and `server.crt` respectively. + +Substitute the correct values for your environment into the environmental variables in the example below: + +``` +# this variable is the name of the cluster being created +export pgo_cluster_name=hippo +# this variable is the namespace the cluster is being deployed into +export cluster_namespace=pgo +# this is the local path to where you stored the CA and server key and certificate +export cluster_tls_asset_path=/path/to + +# create the CA secret. if this already exists, it's OK if it fails +kubectl create secret generic postgresql-ca -n "${cluster_namespace}" \ + --from-file="ca.crt=${cluster_tls_asset_path}/ca.crt" + +# create the server key/certificate secret +kubectl create secret tls "${pgo_cluster_name}-tls-keypair" -n "${cluster_namespace}" \ + --cert="${cluster_tls_asset_path}/server.crt" \ + --key="${cluster_tls_asset_path}/server.key" +``` + +#### Step 2: Create the PostgreSQL Cluster + +The below example uses the Secrets created in the previous step and creates a TLS-enabled PostgreSQL cluster. Additionally, this example sets the `tlsOnly` attribute to `true`, which requires all TCP connections to occur over TLS: + +``` +# this variable is the name of the cluster being created +export pgo_cluster_name=hippo +# this variable is the namespace the cluster is being deployed into +export cluster_namespace=pgo + +cat <<-EOF > "${pgo_cluster_name}-pgcluster.yaml" +apiVersion: crunchydata.com/v1 +kind: Pgcluster +metadata: + annotations: + current-primary: ${pgo_cluster_name} + labels: + autofail: "true" + crunchy-pgbadger: "false" + crunchy-pgha-scope: ${pgo_cluster_name} + deployment-name: ${pgo_cluster_name} + name: ${pgo_cluster_name} + pg-cluster: ${pgo_cluster_name} + pg-pod-anti-affinity: "" + pgo-version: {{< param operatorVersion >}} + pgouser: admin + name: ${pgo_cluster_name} + namespace: ${cluster_namespace} +spec: + BackrestStorage: + accessmode: ReadWriteMany + matchLabels: "" + name: "" + size: 1G + storageclass: "" + storagetype: create + supplementalgroups: "" + PrimaryStorage: + accessmode: ReadWriteMany + matchLabels: "" + name: ${pgo_cluster_name} + size: 1G + storageclass: "" + storagetype: create + supplementalgroups: "" + ReplicaStorage: + accessmode: ReadWriteMany + matchLabels: "" + name: "" + size: 1G + storageclass: "" + storagetype: create + supplementalgroups: "" + annotations: {} + ccpimage: crunchy-postgres-ha + ccpimageprefix: registry.developers.crunchydata.com/crunchydata + ccpimagetag: {{< param centosBase >}}-{{< param postgresVersion >}}-{{< param operatorVersion >}} + clustername: ${pgo_cluster_name} + database: ${pgo_cluster_name} + exporterport: "9187" + limits: {} + name: ${pgo_cluster_name} + namespace: ${cluster_namespace} + pgDataSource: + restoreFrom: "" + restoreOpts: "" + pgbadgerport: "10000" + pgoimageprefix: registry.developers.crunchydata.com/crunchydata + podAntiAffinity: + default: preferred + pgBackRest: preferred + pgBouncer: preferred + port: "5432" + tls: + caSecret: postgresql-ca + tlsSecret: ${pgo_cluster_name}-tls-keypair + tlsOnly: true + user: hippo + userlabels: + pgo-version: {{< param operatorVersion >}} +EOF + +kubectl apply -f "${pgo_cluster_name}-pgcluster.yaml" +``` + ### Modify a Cluster There following modification operations are supported on the From 0c9905f9b866d1831fd07cbdfe19857730d7dcdc Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 28 Dec 2020 15:27:29 -0500 Subject: [PATCH 085/276] Update comment around applying Tolerations The comment was a bit confusing, likely as it was written in haste. It now more clearly states the meaning of the conditional and what it is for. --- internal/operator/cluster/cluster.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/operator/cluster/cluster.go b/internal/operator/cluster/cluster.go index 0c5ddda5c2..8bf6e19884 100644 --- a/internal/operator/cluster/cluster.go +++ b/internal/operator/cluster/cluster.go @@ -642,8 +642,10 @@ func UpdateTolerations(clientset kubeapi.Interface, cluster *crv1.Pgcluster, dep return err } - // if the instance does have specific tolerations, exit here as we do not - // want to override them + // "replica" instances can have toleration overrides. these get managed as + // part of the pgreplicas controller, not here. as such, if this "replica" + // instance has specific toleration overrides, we will exit here so we do not + // apply the cluster-wide tolerations if len(instance.Spec.Tolerations) != 0 { return nil } From 6e572121fd051e64263bd5591e8374a730f4fd58 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 28 Dec 2020 16:45:30 -0500 Subject: [PATCH 086/276] Add automated test workflow While there needs to be a greater repository of tests built up, this does provide assurance over the existing codebase and encouragement for adding more tests. Co-authored-by: Chris Bandy --- .github/workflows/test.yaml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/test.yaml diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000000..ddb05d51cb --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,17 @@ +on: + pull_request: + branches: + - master + push: + branches: + - master + +jobs: + go-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-go@v2 + with: + go-version: 1.x + - run: PGOROOT=$(pwd) go test ./... From 99efd152e7726048d5a5260670d4ff1f8220d555 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 29 Dec 2020 09:59:45 -0500 Subject: [PATCH 087/276] Ensure sync replication ConfigMap is deleted on delete The deletion task was not checking for the existence of this ConfigMap on delete, and as such it was being missed. --- cmd/pgo-rmdata/process.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/pgo-rmdata/process.go b/cmd/pgo-rmdata/process.go index b1eba3bc95..3449476170 100644 --- a/cmd/pgo-rmdata/process.go +++ b/cmd/pgo-rmdata/process.go @@ -43,6 +43,7 @@ const ( configConfigMapSuffix = "config" leaderConfigMapSuffix = "leader" failoverConfigMapSuffix = "failover" + syncConfigMapSuffix = "sync" ) func Delete(request Request) { @@ -235,6 +236,8 @@ func removeClusterConfigmaps(request Request) { // next, the name of the failover configmap, which is // "`clusterName`-failover" fmt.Sprintf("%s-%s", request.ClusterName, failoverConfigMapSuffix), + // next, if there is a synchronous replication configmap, clean that up + fmt.Sprintf("%s-%s", request.ClusterName, syncConfigMapSuffix), // finally, if there is a pgbouncer, remove the pgbouncer configmap util.GeneratePgBouncerConfigMapName(request.ClusterName), } From 68f89be068346691956a34378eff01977a8ec593 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Wed, 30 Dec 2020 17:44:14 -0500 Subject: [PATCH 088/276] Bump pgBackRest to 2.31 Issue: [ch9995] --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1411047cb5..5aa5bae77d 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) PGO_VERSION ?= 4.5.1 PGO_PG_VERSION ?= 12 PGO_PG_FULLVERSION ?= 12.5 -PGO_BACKREST_VERSION ?= 2.29 +PGO_BACKREST_VERSION ?= 2.31 PACKAGER ?= yum RELTMPDIR=/tmp/release.$(PGO_VERSION) From 50e2dde2eea44cf4ad41030b7c0a2201b9f52c4a Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 28 Dec 2020 21:48:16 -0500 Subject: [PATCH 089/276] Manage Service port for replicas for monitoring The update functions for adding/removing a metrics collection sidecar were not managing the port on the replica service. This patch rectifies that by ensuring all PostgreSQL Services under management by the Operator have their exporter port managed. --- internal/operator/cluster/clusterlogic.go | 2 +- internal/operator/cluster/common.go | 16 +++++ internal/operator/cluster/exporter.go | 85 +++++++++++++---------- 3 files changed, 66 insertions(+), 37 deletions(-) diff --git a/internal/operator/cluster/clusterlogic.go b/internal/operator/cluster/clusterlogic.go index e527df2cd4..6e26bf35a3 100644 --- a/internal/operator/cluster/clusterlogic.go +++ b/internal/operator/cluster/clusterlogic.go @@ -390,7 +390,7 @@ func scaleReplicaCreateMissingService(clientset kubernetes.Interface, replica *c // only add references to the exporter / pgBadger ports clusterLabels := cluster.ObjectMeta.GetLabels() - if val, ok := clusterLabels[config.LABEL_EXPORTER]; ok && val == config.LABEL_TRUE { + if cluster.Spec.Exporter { serviceFields.ExporterPort = cluster.Spec.ExporterPort } diff --git a/internal/operator/cluster/common.go b/internal/operator/cluster/common.go index 82bf11c3de..bbd497e582 100644 --- a/internal/operator/cluster/common.go +++ b/internal/operator/cluster/common.go @@ -16,16 +16,20 @@ package cluster */ import ( + "context" "fmt" "strings" + "github.com/crunchydata/postgres-operator/internal/config" "github.com/crunchydata/postgres-operator/internal/kubeapi" "github.com/crunchydata/postgres-operator/internal/operator" pgpassword "github.com/crunchydata/postgres-operator/internal/postgres/password" "github.com/crunchydata/postgres-operator/internal/util" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" + log "github.com/sirupsen/logrus" v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) @@ -81,6 +85,18 @@ func generatePassword() (string, error) { return util.GeneratePassword(generatedPasswordLength) } +// getClusterInstanceServices gets all of the services applicable to each +// PostgreSQL instances +func getClusterInstanceServices(clientset kubernetes.Interface, cluster *crv1.Pgcluster) (*v1.ServiceList, error) { + ctx := context.TODO() + options := metav1.ListOptions{ + LabelSelector: fmt.Sprintf("%s=%s,!%s", + config.LABEL_PG_CLUSTER, cluster.Name, config.LABEL_PGO_BACKREST_REPO), + } + + return clientset.CoreV1().Services(cluster.Namespace).List(ctx, options) +} + // makePostgreSQLPassword creates the expected hash for a password type for a // PostgreSQL password // nolint:unparam // this is set up to accept SCRAM in the not-too-distant future diff --git a/internal/operator/cluster/exporter.go b/internal/operator/cluster/exporter.go index e02fdf6bfe..c57d953f38 100644 --- a/internal/operator/cluster/exporter.go +++ b/internal/operator/cluster/exporter.go @@ -66,37 +66,45 @@ func AddExporter(clientset kubernetes.Interface, restconfig *rest.Config, cluste return err } - // set up the Service, which is still needed on a standby - svc, err := clientset.CoreV1().Services(cluster.Namespace).Get(ctx, cluster.Name, metav1.GetOptions{}) + // set up the Services, which are still needed on a standby + services, err := getClusterInstanceServices(clientset, cluster) if err != nil { return err } - // loop over the service ports to see if exporter port is already set up. if - // it is, we can return from there - for _, svcPort := range svc.Spec.Ports { - if svcPort.Name == exporterServicePortName { - return nil + // loop over each service to perform the necessary modifications +svcLoop: + for i := range services.Items { + svc := &services.Items[i] + + // loop over the service ports to see if exporter port is already set up. if + // it is, we can continue and skip the outerloop + for _, svcPort := range svc.Spec.Ports { + if svcPort.Name == exporterServicePortName { + continue svcLoop + } } - } - // otherwise, we need to append a service port to the list - port, err := strconv.ParseInt( - util.GetValueOrDefault(cluster.Spec.ExporterPort, operator.Pgo.Cluster.ExporterPort), 10, 32) - if err != nil { - return err - } + // otherwise, we need to append a service port to the list + port, err := strconv.ParseInt( + util.GetValueOrDefault(cluster.Spec.ExporterPort, operator.Pgo.Cluster.ExporterPort), 10, 32) + // if we can't parse this for whatever reason, issue a warning and continue on + if err != nil { + log.Warn(err) + } - svcPort := v1.ServicePort{ - Name: exporterServicePortName, - Protocol: v1.ProtocolTCP, - Port: int32(port), - } + svcPort := v1.ServicePort{ + Name: exporterServicePortName, + Protocol: v1.ProtocolTCP, + Port: int32(port), + } - svc.Spec.Ports = append(svc.Spec.Ports, svcPort) + svc.Spec.Ports = append(svc.Spec.Ports, svcPort) - if _, err := clientset.CoreV1().Services(svc.Namespace).Update(ctx, svc, metav1.UpdateOptions{}); err != nil { - return err + // if we fail to update the service, warn, but continue on + if _, err := clientset.CoreV1().Services(svc.Namespace).Update(ctx, svc, metav1.UpdateOptions{}); err != nil { + log.Warn(err) + } } // this can't be installed if this is a standby, so abort if that's the case @@ -111,7 +119,7 @@ func AddExporter(clientset kubernetes.Interface, restconfig *rest.Config, cluste return err } - // add the m onitoring user and all the views associated with this + // add the monitoring user and all the views associated with this // user. this can be done by executing a script on the container itself cmd := []string{"/bin/bash", exporterInstallScript} @@ -188,27 +196,32 @@ func CreateExporterSecret(clientset kubernetes.Interface, cluster *crv1.Pgcluste func RemoveExporter(clientset kubernetes.Interface, restconfig *rest.Config, cluster *crv1.Pgcluster) error { ctx := context.TODO() - // close the service port - svc, err := clientset.CoreV1().Services(cluster.Namespace).Get(ctx, cluster.Name, metav1.GetOptions{}) + // close the exporter port on each service + services, err := getClusterInstanceServices(clientset, cluster) if err != nil { return err } - svcPorts := []v1.ServicePort{} + for i := range services.Items { + svc := &services.Items[i] + svcPorts := []v1.ServicePort{} - for _, svcPort := range svc.Spec.Ports { - // if we find the service port for the exporter, skip it in the loop - if svcPort.Name == exporterServicePortName { - continue - } + for _, svcPort := range svc.Spec.Ports { + // if we find the service port for the exporter, skip it in the loop, but + // as we will not be including it in the update + if svcPort.Name == exporterServicePortName { + continue + } - svcPorts = append(svcPorts, svcPort) - } + svcPorts = append(svcPorts, svcPort) + } - svc.Spec.Ports = svcPorts + svc.Spec.Ports = svcPorts - if _, err := clientset.CoreV1().Services(svc.Namespace).Update(ctx, svc, metav1.UpdateOptions{}); err != nil { - return err + // if we fail to update the service, warn but continue + if _, err := clientset.CoreV1().Services(svc.Namespace).Update(ctx, svc, metav1.UpdateOptions{}); err != nil { + log.Warn(err) + } } // disable the user before clearing the Secret, so there does not end up being From 738491e3cadb1c7e83bf9558c4c0bb85950267c8 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 28 Dec 2020 22:18:00 -0500 Subject: [PATCH 090/276] Convert pgBadger enablement to CRD attribute The pgBadger sidecar is now enabled on creation by setting the `pgBadger` attribute on the `pgclusters.crunchydata.com` CRD instead of a label on a custom resource. --- docs/content/custom-resources/_index.md | 3 +- .../apiserver/clusterservice/clusterimpl.go | 9 ++-- internal/config/labels.go | 2 - internal/operator/cluster/clusterlogic.go | 14 ++---- internal/operator/cluster/upgrade.go | 7 +++ internal/operator/clusterutilities.go | 47 ++++++++++--------- pkg/apis/crunchydata.com/v1/cluster.go | 4 +- 7 files changed, 46 insertions(+), 40 deletions(-) diff --git a/docs/content/custom-resources/_index.md b/docs/content/custom-resources/_index.md index f327e17667..e1d256bd64 100644 --- a/docs/content/custom-resources/_index.md +++ b/docs/content/custom-resources/_index.md @@ -756,7 +756,8 @@ make changes, as described below. | Limits | `create`, `update` | Specify the container resource limits that the PostgreSQL cluster should use. Follows the [Kubernetes definitions of resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | | Name | `create` | The name of the PostgreSQL instance that is the primary. On creation, this should be set to be the same as `ClusterName`. | | Namespace | `create` | The Kubernetes Namespace that the PostgreSQL cluster is deployed in. | -| PGBadgerPort | `create` | If the `"crunchy-pgbadger"` label is set in `UserLabels`, then this specifies the port that the pgBadger sidecar runs on (e.g. `10000`) | +| PGBadger | `create` | If `true`, deploys the `crunchy-pgbadger` sidecar for query analysis. | +| PGBadgerPort | `create` | If the `PGBadger` label is set, then this specifies the port that the pgBadger sidecar runs on (e.g. `10000`) | | PGDataSource | `create` | Used to indicate if a PostgreSQL cluster should bootstrap its data from a pgBackRest repository. This uses the PostgreSQL Data Source Specification, described below. | | PGOImagePrefix | `create` | If provided, the image prefix (or registry) of any PostgreSQL Operator images that are used for jobs, e.g. `registry.developers.crunchydata.com/crunchydata`. The default is to use the image prefix set in the PostgreSQL Operator configuration. | | PgBouncer | `create`, `update` | If specified, defines the attributes to use for the pgBouncer connection pooling deployment that can be used in conjunction with this PostgreSQL cluster. Please see the specification defined below. | diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index ee8b01e0d6..f92777fd0e 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -1496,11 +1496,10 @@ func getClusterParams(request *msgs.CreateClusterRequest, name string, userLabel // set the pgBackRest repository path spec.BackrestRepoPath = request.BackrestRepoPath - // pgbadger - set with global flag first then check for a user flag - labels[config.LABEL_BADGER] = strconv.FormatBool(apiserver.BadgerFlag) - if request.BadgerFlag { - labels[config.LABEL_BADGER] = "true" - } + // enable the pgBadger sidecar based on the what the user passed in or what + // the default value is. the user value takes precedence, unless it's false, + // as the legacy check only looked for enablement + spec.PGBadger = request.BadgerFlag || apiserver.BadgerFlag newInstance := &crv1.Pgcluster{ ObjectMeta: metav1.ObjectMeta{ diff --git a/internal/config/labels.go b/internal/config/labels.go index d6d851eb52..5b15db75fb 100644 --- a/internal/config/labels.go +++ b/internal/config/labels.go @@ -92,8 +92,6 @@ const ( LABEL_BACKREST_PITR_TARGET = "backrest-pitr-target" LABEL_BACKREST_STORAGE_TYPE = "backrest-storage-type" LABEL_BACKREST_S3_VERIFY_TLS = "backrest-s3-verify-tls" - LABEL_BADGER = "crunchy-pgbadger" - LABEL_BADGER_CCPIMAGE = "crunchy-pgbadger" LABEL_BACKUP_TYPE_BACKREST = "pgbackrest" LABEL_BACKUP_TYPE_PGDUMP = "pgdump" ) diff --git a/internal/operator/cluster/clusterlogic.go b/internal/operator/cluster/clusterlogic.go index 6e26bf35a3..517f6181a2 100644 --- a/internal/operator/cluster/clusterlogic.go +++ b/internal/operator/cluster/clusterlogic.go @@ -64,10 +64,8 @@ func addClusterCreateMissingService(clientset kubernetes.Interface, cl *crv1.Pgc ServiceType: st, } - // only add references to the exporter / pgBadger ports - clusterLabels := cl.ObjectMeta.GetLabels() - - if val, ok := clusterLabels[config.LABEL_BADGER]; ok && val == config.LABEL_TRUE { + // set the pgBadger port if pgBadger is enabled + if cl.Spec.PGBadger { serviceFields.PGBadgerPort = cl.Spec.PGBadgerPort } @@ -323,7 +321,7 @@ func getClusterDeploymentFields(clientset kubernetes.Interface, ContainerResources: operator.GetResourcesJSON(cl.Spec.Resources, cl.Spec.Limits), ConfVolume: operator.GetConfVolume(clientset, cl, namespace), ExporterAddon: operator.GetExporterAddon(cl.Spec), - BadgerAddon: operator.GetBadgerAddon(clientset, namespace, cl, cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY]), + BadgerAddon: operator.GetBadgerAddon(clientset, cl, cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY]), PgmonitorEnvVars: operator.GetPgmonitorEnvVars(cl), ScopeLabel: config.LABEL_PGHA_SCOPE, PgbackrestEnvVars: operator.GetPgbackrestEnvVars(cl, cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY], @@ -388,13 +386,11 @@ func scaleReplicaCreateMissingService(clientset kubernetes.Interface, replica *c } // only add references to the exporter / pgBadger ports - clusterLabels := cluster.ObjectMeta.GetLabels() - if cluster.Spec.Exporter { serviceFields.ExporterPort = cluster.Spec.ExporterPort } - if val, ok := clusterLabels[config.LABEL_BADGER]; ok && val == config.LABEL_TRUE { + if cluster.Spec.PGBadger { serviceFields.PGBadgerPort = cluster.Spec.PGBadgerPort } @@ -470,7 +466,7 @@ func scaleReplicaCreateDeployment(clientset kubernetes.Interface, NodeSelector: operator.GetAffinity(replica.Spec.UserLabels["NodeLabelKey"], replica.Spec.UserLabels["NodeLabelValue"], "In"), PodAntiAffinity: operator.GetPodAntiAffinity(cluster, crv1.PodAntiAffinityDeploymentDefault, cluster.Spec.PodAntiAffinity.Default), ExporterAddon: operator.GetExporterAddon(cluster.Spec), - BadgerAddon: operator.GetBadgerAddon(clientset, namespace, cluster, replica.Spec.Name), + BadgerAddon: operator.GetBadgerAddon(clientset, cluster, replica.Spec.Name), ScopeLabel: config.LABEL_PGHA_SCOPE, PgbackrestEnvVars: operator.GetPgbackrestEnvVars(cluster, replica.Spec.Name, cluster.Spec.Port, cluster.Spec.UserLabels[config.LABEL_BACKREST_STORAGE_TYPE]), diff --git a/internal/operator/cluster/upgrade.go b/internal/operator/cluster/upgrade.go index 0b12c5c4ff..f46ed64671 100644 --- a/internal/operator/cluster/upgrade.go +++ b/internal/operator/cluster/upgrade.go @@ -483,6 +483,13 @@ func preparePgclusterForUpgrade(pgcluster *crv1.Pgcluster, parameters map[string delete(pgcluster.Spec.UserLabels, config.LABEL_EXPORTER) } + // 4.6.0 moved pgBadger to use an attribute instead of a label. If this label + // exists on the current CRD, move the value to the attribute. + if _, ok := pgcluster.ObjectMeta.GetLabels()["crunchy-pgbadger"]; ok { + pgcluster.Spec.PGBadger = true + delete(pgcluster.ObjectMeta.Labels, "crunchy-pgbadger") + } + // since the current primary label is not used in this version of the Postgres Operator, // delete it before moving on to other upgrade tasks delete(pgcluster.ObjectMeta.Labels, config.LABEL_CURRENT_PRIMARY) diff --git a/internal/operator/clusterutilities.go b/internal/operator/clusterutilities.go index 3281a43c7e..cfa6351408 100644 --- a/internal/operator/clusterutilities.go +++ b/internal/operator/clusterutilities.go @@ -328,30 +328,33 @@ func GetBackrestDeployment(clientset kubernetes.Interface, cluster *crv1.Pgclust return deployment, err } -func GetBadgerAddon(clientset kubernetes.Interface, namespace string, cluster *crv1.Pgcluster, pgbadger_target string) string { - spec := cluster.Spec - - if cluster.Labels[config.LABEL_BADGER] == "true" { - log.Debug("crunchy_badger was found as a label on cluster create") - badgerTemplateFields := badgerTemplateFields{} - badgerTemplateFields.CCPImageTag = util.GetStandardImageTag(spec.CCPImage, spec.CCPImageTag) - badgerTemplateFields.BadgerTarget = pgbadger_target - badgerTemplateFields.PGBadgerPort = spec.PGBadgerPort - badgerTemplateFields.CCPImagePrefix = util.GetValueOrDefault(spec.CCPImagePrefix, Pgo.Cluster.CCPImagePrefix) - - var badgerDoc bytes.Buffer - err := config.BadgerTemplate.Execute(&badgerDoc, badgerTemplateFields) - if err != nil { - log.Error(err.Error()) - return "" - } +// GetBadgerAddon is a legacy method that generates a JSONish string to be used +// to add a pgBadger sidecar to a PostgreSQL instance +func GetBadgerAddon(clientset kubernetes.Interface, cluster *crv1.Pgcluster, target string) string { + if !cluster.Spec.PGBadger { + return "" + } - if CRUNCHY_DEBUG { - _ = config.BadgerTemplate.Execute(os.Stdout, badgerTemplateFields) - } - return badgerDoc.String() + log.Debugf("pgBadger enabled for cluster %q", cluster.Name) + + badgerTemplateFields := badgerTemplateFields{ + BadgerTarget: target, + CCPImagePrefix: util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, Pgo.Cluster.CCPImagePrefix), + CCPImageTag: util.GetStandardImageTag(cluster.Spec.CCPImage, cluster.Spec.CCPImageTag), + PGBadgerPort: cluster.Spec.PGBadgerPort, + } + + if CRUNCHY_DEBUG { + _ = config.BadgerTemplate.Execute(os.Stdout, badgerTemplateFields) + } + + doc := bytes.Buffer{} + if err := config.BadgerTemplate.Execute(&doc, badgerTemplateFields); err != nil { + log.Error(err) + return "" } - return "" + + return doc.String() } // GetExporterAddon returns the template used to create an exporter container diff --git a/pkg/apis/crunchydata.com/v1/cluster.go b/pkg/apis/crunchydata.com/v1/cluster.go index d89e10f6c5..72b26cbc0e 100644 --- a/pkg/apis/crunchydata.com/v1/cluster.go +++ b/pkg/apis/crunchydata.com/v1/cluster.go @@ -49,7 +49,9 @@ type PgclusterSpec struct { CCPImagePrefix string `json:"ccpimageprefix"` PGOImagePrefix string `json:"pgoimageprefix"` Port string `json:"port"` - PGBadgerPort string `json:"pgbadgerport"` + // PGBadger, if set to true, enables the pgBadger sidecar + PGBadger bool `json:"pgBadger"` + PGBadgerPort string `json:"pgbadgerport"` // Exporter, if set to true, enables the exporter sidecar Exporter bool `json:"exporter"` ExporterPort string `json:"exporterport"` From d7215679b42f0c1c7615cc6033a3aedeee10ce6b Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 28 Dec 2020 23:05:49 -0500 Subject: [PATCH 091/276] Allow for pgBadger sidecar to be enabled/disabled Similar to the metrics collection sidecar, the pgBadger sidecar can now be enabled during the lifetime of a PostgreSQL cluster. This also adds the `--enable-pgbadger` and `--disable-pgbadger` flags to the `pgo update cluster` command. Additionally, the update to the pgBadger sidecar can be triggered through a modification to the `pgclusters.crunchydata.com` CR, and will trigger a rolling update to minimize downtime. --- cmd/pgo/cmd/cluster.go | 7 + cmd/pgo/cmd/update.go | 12 ++ docs/content/custom-resources/_index.md | 2 +- .../reference/pgo_update_cluster.md | 12 +- .../files/pgo-configs/cluster-deployment.json | 6 +- .../files/pgo-configs/pgbadger.json | 82 ++++---- .../apiserver/clusterservice/clusterimpl.go | 9 + .../pgcluster/pgclustercontroller.go | 19 ++ internal/operator/cluster/cluster.go | 9 +- internal/operator/cluster/clusterlogic.go | 4 +- internal/operator/cluster/pgbadger.go | 199 ++++++++++++++++++ internal/operator/clusterutilities.go | 2 +- pkg/apiservermsgs/clustermsgs.go | 15 +- 13 files changed, 322 insertions(+), 56 deletions(-) create mode 100644 internal/operator/cluster/pgbadger.go diff --git a/cmd/pgo/cmd/cluster.go b/cmd/pgo/cmd/cluster.go index de7b895de4..0e9d2b739c 100644 --- a/cmd/pgo/cmd/cluster.go +++ b/cmd/pgo/cmd/cluster.go @@ -708,6 +708,13 @@ func updateCluster(args []string, ns string) { r.Metrics = msgs.UpdateClusterMetricsDisable } + // check to see if the pgBadger sidecar needs to be enabled or disabled + if EnablePGBadger { + r.PGBadger = msgs.UpdateClusterPGBadgerEnable + } else if DisablePGBadger { + r.PGBadger = msgs.UpdateClusterPGBadgerDisable + } + // if the user provided resources for CPU or Memory, validate them to ensure // they are valid Kubernetes values if err := util.ValidateQuantity(r.CPURequest, "cpu"); err != nil { diff --git a/cmd/pgo/cmd/update.go b/cmd/pgo/cmd/update.go index f5c5d6eb82..c11e352094 100644 --- a/cmd/pgo/cmd/update.go +++ b/cmd/pgo/cmd/update.go @@ -31,11 +31,15 @@ var ( DisableLogin bool // DisableMetrics allows a user to disable metrics collection DisableMetrics bool + // DisablePGBadger allows a user to disable pgBadger + DisablePGBadger bool // EnableLogin allows a user to enable the ability for a PostgreSQL uesr to // log in EnableLogin bool // EnableMetrics allows a user to enbale metrics collection EnableMetrics bool + // EnablePGBadger allows a user to enbale pgBadger + EnablePGBadger bool // ExpireUser sets a user to having their password expired ExpireUser bool // ExporterRotatePassword rotates the password for the designed PostgreSQL @@ -92,6 +96,8 @@ func init() { UpdateClusterCmd.Flags().BoolVar(&DisableAutofailFlag, "disable-autofail", false, "Disables autofail capabitilies in the cluster.") UpdateClusterCmd.Flags().BoolVar(&DisableMetrics, "disable-metrics", false, "Disable the metrics collection sidecar. May cause brief downtime.") + UpdateClusterCmd.Flags().BoolVar(&DisablePGBadger, "disable-pgbadger", false, + "Disable the pgBadger sidecar. May cause brief downtime.") UpdateClusterCmd.Flags().BoolVar(&EnableAutofailFlag, "enable-autofail", false, "Enables autofail capabitilies in the cluster.") UpdateClusterCmd.Flags().StringVar(&MemoryRequest, "memory", "", "Set the amount of RAM to request, e.g. "+ "1GiB.") @@ -118,6 +124,8 @@ func init() { "the Crunchy Postgres Exporter sidecar container.") UpdateClusterCmd.Flags().BoolVar(&EnableMetrics, "enable-metrics", false, "Enable the metrics collection sidecar. May cause brief downtime.") + UpdateClusterCmd.Flags().BoolVar(&EnablePGBadger, "enable-pgbadger", false, + "Enable the pgBadger sidecar. May cause brief downtime.") UpdateClusterCmd.Flags().BoolVar(&ExporterRotatePassword, "exporter-rotate-password", false, "Used to rotate the password for the metrics collection agent.") UpdateClusterCmd.Flags().BoolVarP(&EnableStandby, "enable-standby", "", false, "Enables standby mode in the cluster(s) specified.") @@ -260,6 +268,10 @@ var UpdateClusterCmd = &cobra.Command{ fmt.Println("Adding or removing a metrics collection sidecar can cause downtime.") } + if EnablePGBadger || DisablePGBadger { + fmt.Println("Adding or removing a pgBadger sidecar can cause downtime.") + } + if len(Tablespaces) > 0 { fmt.Println("Adding tablespaces can cause downtime.") } diff --git a/docs/content/custom-resources/_index.md b/docs/content/custom-resources/_index.md index e1d256bd64..d70831814b 100644 --- a/docs/content/custom-resources/_index.md +++ b/docs/content/custom-resources/_index.md @@ -756,7 +756,7 @@ make changes, as described below. | Limits | `create`, `update` | Specify the container resource limits that the PostgreSQL cluster should use. Follows the [Kubernetes definitions of resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | | Name | `create` | The name of the PostgreSQL instance that is the primary. On creation, this should be set to be the same as `ClusterName`. | | Namespace | `create` | The Kubernetes Namespace that the PostgreSQL cluster is deployed in. | -| PGBadger | `create` | If `true`, deploys the `crunchy-pgbadger` sidecar for query analysis. | +| PGBadger | `create`,`update` | If `true`, deploys the `crunchy-pgbadger` sidecar for query analysis. | | PGBadgerPort | `create` | If the `PGBadger` label is set, then this specifies the port that the pgBadger sidecar runs on (e.g. `10000`) | | PGDataSource | `create` | Used to indicate if a PostgreSQL cluster should bootstrap its data from a pgBackRest repository. This uses the PostgreSQL Data Source Specification, described below. | | PGOImagePrefix | `create` | If provided, the image prefix (or registry) of any PostgreSQL Operator images that are used for jobs, e.g. `registry.developers.crunchydata.com/crunchydata`. The default is to use the image prefix set in the PostgreSQL Operator configuration. | diff --git a/docs/content/pgo-client/reference/pgo_update_cluster.md b/docs/content/pgo-client/reference/pgo_update_cluster.md index e480bf767f..4622833460 100644 --- a/docs/content/pgo-client/reference/pgo_update_cluster.md +++ b/docs/content/pgo-client/reference/pgo_update_cluster.md @@ -25,7 +25,7 @@ pgo update cluster [flags] --annotation strings Add an Annotation to all of the managed deployments (PostgreSQL, pgBackRest, pgBouncer) The format to add an annotation is "name=value" The format to remove an annotation is "name-" - + For example, to add two annotations: "--annotation=hippo=awesome,elephant=cool" --annotation-pgbackrest strings Add an Annotation specifically to pgBackRest deployments The format to add an annotation is "name=value" @@ -39,8 +39,10 @@ pgo update cluster [flags] --cpu-limit string Set the number of millicores to limit for the CPU, e.g. "100m" or "0.1". --disable-autofail Disables autofail capabitilies in the cluster. --disable-metrics Disable the metrics collection sidecar. May cause brief downtime. + --disable-pgbadger Disable the pgBadger sidecar. May cause brief downtime. --enable-autofail Enables autofail capabitilies in the cluster. --enable-metrics Enable the metrics collection sidecar. May cause brief downtime. + --enable-pgbadger Enable the pgBadger sidecar. May cause brief downtime. --enable-standby Enables standby mode in the cluster(s) specified. --exporter-cpu string Set the number of millicores to request for CPU for the Crunchy Postgres Exporter sidecar container, e.g. "100m" or "0.1". --exporter-cpu-limit string Set the number of millicores to limit for CPU for the Crunchy Postgres Exporter sidecar container, e.g. "100m" or "0.1". @@ -60,13 +62,13 @@ pgo update cluster [flags] --shutdown Shutdown the database cluster if it is currently running. --startup Restart the database cluster if it is currently shutdown. --tablespace strings Add a PostgreSQL tablespace on the cluster, e.g. "name=ts1:storageconfig=nfsstorage". The format is a key/value map that is delimited by "=" and separated by ":". The following parameters are available: - + - name (required): the name of the PostgreSQL tablespace - storageconfig (required): the storage configuration to use, as specified in the list available in the "pgo-config" ConfigMap (aka "pgo.yaml") - pvcsize: the size of the PVC capacity, which overrides the value set in the specified storageconfig. Follows the Kubernetes quantity format. - + For example, to create a tablespace with the NFS storage configuration with a PVC of size 10GiB: - + --tablespace=name=ts1:storageconfig=nfsstorage:pvcsize=10Gi ``` @@ -87,4 +89,4 @@ pgo update cluster [flags] * [pgo update](/pgo-client/reference/pgo_update/) - Update a pgouser, pgorole, or cluster -###### Auto generated by spf13/cobra on 16-Dec-2020 +###### Auto generated by spf13/cobra on 28-Dec-2020 diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json index f5fb452849..8499739ccf 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json @@ -206,9 +206,9 @@ {{ if .ExporterAddon }} ,{{.ExporterAddon }} {{ end }} - - {{.BadgerAddon }} - + {{ if .BadgerAddon }} + ,{{.BadgerAddon }} + {{ end }} ], "volumes": [{ "name": "pgdata", diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbadger.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbadger.json index d9b04daa73..ce03aaabb7 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbadger.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbadger.json @@ -1,41 +1,41 @@ - ,{ - "name": "pgbadger", - "image": "{{.CCPImagePrefix}}/crunchy-pgbadger:{{.CCPImageTag}}", - "ports": [ { - "containerPort": {{.PGBadgerPort}}, - "protocol": "TCP" - } - ], - "readinessProbe": { - "tcpSocket": { - "port": {{.PGBadgerPort}} - }, - "initialDelaySeconds": 20, - "periodSeconds": 10 - }, - "env": [ { - "name": "BADGER_TARGET", - "value": "{{.BadgerTarget}}" - }, { - "name": "PGBADGER_SERVICE_PORT", - "value": "{{.PGBadgerPort}}" - } ], - "resources": { - "limits": { - "cpu": "500m", - "memory": "64Mi" - } - }, - "volumeMounts": [ - { - "mountPath": "/pgdata", - "name": "pgdata", - "readOnly": true - }, - { - "mountPath": "/report", - "name": "report", - "readOnly": false - } - ] - } +{ + "name": "pgbadger", + "image": "{{.CCPImagePrefix}}/crunchy-pgbadger:{{.CCPImageTag}}", + "ports": [ { + "containerPort": {{.PGBadgerPort}}, + "protocol": "TCP" + } + ], + "readinessProbe": { + "tcpSocket": { + "port": {{.PGBadgerPort}} + }, + "initialDelaySeconds": 20, + "periodSeconds": 10 + }, + "env": [ { + "name": "BADGER_TARGET", + "value": "{{.BadgerTarget}}" + }, { + "name": "PGBADGER_SERVICE_PORT", + "value": "{{.PGBadgerPort}}" + } ], + "resources": { + "limits": { + "cpu": "500m", + "memory": "64Mi" + } + }, + "volumeMounts": [ + { + "mountPath": "/pgdata", + "name": "pgdata", + "readOnly": true + }, + { + "mountPath": "/report", + "name": "report", + "readOnly": false + } + ] +} diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index f92777fd0e..d93d7f4e4f 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -1903,6 +1903,15 @@ func UpdateCluster(request *msgs.UpdateClusterRequest) msgs.UpdateClusterRespons case msgs.UpdateClusterMetricsDoNothing: // this is never reached -- no-op } + // enable or disable the pgBadger sidecar + switch request.PGBadger { + case msgs.UpdateClusterPGBadgerEnable: + cluster.Spec.PGBadger = true + case msgs.UpdateClusterPGBadgerDisable: + cluster.Spec.PGBadger = false + case msgs.UpdateClusterPGBadgerDoNothing: // this is never reached -- no-op + } + // enable or disable standby mode based on UpdateClusterStandbyStatus provided in // the request switch request.Standby { diff --git a/internal/controller/pgcluster/pgclustercontroller.go b/internal/controller/pgcluster/pgclustercontroller.go index 02789dce9b..d2c910ad99 100644 --- a/internal/controller/pgcluster/pgclustercontroller.go +++ b/internal/controller/pgcluster/pgclustercontroller.go @@ -255,6 +255,25 @@ func (c *Controller) onUpdate(oldObj, newObj interface{}) { } } + // see if we are adding / removing the pgBadger sidecar + if oldcluster.Spec.PGBadger != newcluster.Spec.PGBadger { + var err error + + // determine if the sidecar is being enabled/disabled and take the precursor + // actions before the deployment template is modified + if newcluster.Spec.PGBadger { + err = clusteroperator.AddPGBadger(c.Client, c.Client.Config, newcluster) + } else { + err = clusteroperator.RemovePGBadger(c.Client, c.Client.Config, newcluster) + } + + if err == nil { + rollingUpdateFuncs = append(rollingUpdateFuncs, clusteroperator.UpdatePGBadgerSidecar) + } else { + log.Errorf("could not update pgbadger sidecar: %q", err.Error()) + } + } + // see if any of the resource values have changed for the database or exporter container, // if so, update them if !reflect.DeepEqual(oldcluster.Spec.Resources, newcluster.Spec.Resources) || diff --git a/internal/operator/cluster/cluster.go b/internal/operator/cluster/cluster.go index 8bf6e19884..2b292290c5 100644 --- a/internal/operator/cluster/cluster.go +++ b/internal/operator/cluster/cluster.go @@ -59,8 +59,13 @@ type ServiceTemplateFields struct { // ReplicaSuffix ... const ReplicaSuffix = "-replica" -// exporterContainerName is the name of the exporter container -const exporterContainerName = "exporter" +const ( + // exporterContainerName is the name of the exporter container + exporterContainerName = "exporter" + + // pgBadgerContainerName is the name of the pgBadger container + pgBadgerContainerName = "pgbadger" +) func AddClusterBase(clientset kubeapi.Interface, cl *crv1.Pgcluster, namespace string) { ctx := context.TODO() diff --git a/internal/operator/cluster/clusterlogic.go b/internal/operator/cluster/clusterlogic.go index 517f6181a2..f6ed1c52f8 100644 --- a/internal/operator/cluster/clusterlogic.go +++ b/internal/operator/cluster/clusterlogic.go @@ -321,7 +321,7 @@ func getClusterDeploymentFields(clientset kubernetes.Interface, ContainerResources: operator.GetResourcesJSON(cl.Spec.Resources, cl.Spec.Limits), ConfVolume: operator.GetConfVolume(clientset, cl, namespace), ExporterAddon: operator.GetExporterAddon(cl.Spec), - BadgerAddon: operator.GetBadgerAddon(clientset, cl, cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY]), + BadgerAddon: operator.GetBadgerAddon(cl, cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY]), PgmonitorEnvVars: operator.GetPgmonitorEnvVars(cl), ScopeLabel: config.LABEL_PGHA_SCOPE, PgbackrestEnvVars: operator.GetPgbackrestEnvVars(cl, cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY], @@ -466,7 +466,7 @@ func scaleReplicaCreateDeployment(clientset kubernetes.Interface, NodeSelector: operator.GetAffinity(replica.Spec.UserLabels["NodeLabelKey"], replica.Spec.UserLabels["NodeLabelValue"], "In"), PodAntiAffinity: operator.GetPodAntiAffinity(cluster, crv1.PodAntiAffinityDeploymentDefault, cluster.Spec.PodAntiAffinity.Default), ExporterAddon: operator.GetExporterAddon(cluster.Spec), - BadgerAddon: operator.GetBadgerAddon(clientset, cluster, replica.Spec.Name), + BadgerAddon: operator.GetBadgerAddon(cluster, replica.Spec.Name), ScopeLabel: config.LABEL_PGHA_SCOPE, PgbackrestEnvVars: operator.GetPgbackrestEnvVars(cluster, replica.Spec.Name, cluster.Spec.Port, cluster.Spec.UserLabels[config.LABEL_BACKREST_STORAGE_TYPE]), diff --git a/internal/operator/cluster/pgbadger.go b/internal/operator/cluster/pgbadger.go new file mode 100644 index 0000000000..ed1b0fdfc2 --- /dev/null +++ b/internal/operator/cluster/pgbadger.go @@ -0,0 +1,199 @@ +package cluster + +/* + Copyright 2020 Crunchy Data Solutions, Inc. + 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. +*/ + +import ( + "context" + "encoding/json" + "fmt" + "strconv" + + "github.com/crunchydata/postgres-operator/internal/kubeapi" + "github.com/crunchydata/postgres-operator/internal/operator" + "github.com/crunchydata/postgres-operator/internal/util" + crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" + + log "github.com/sirupsen/logrus" + appsv1 "k8s.io/api/apps/v1" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" +) + +const ( + // pgBadgerServicePortName is the name used to identify the pgBadger port in + // the service + pgBadgerServicePortName = "pgbadger" +) + +// AddPGBadger ensures that a PostgreSQL cluster is able to undertake the +// actions required by the "crunchy-badger", i.e. updating the Service. +// This executes regardless if this is a standby cluster. +// +// This does not modify the Deployment that has the pgBadger sidecar. That is +// handled by the "UpdatePGBadgerSidecar" function, so it can be handled as part +// of a rolling update. +func AddPGBadger(clientset kubernetes.Interface, restconfig *rest.Config, cluster *crv1.Pgcluster) error { + ctx := context.TODO() + // set up the Services, which are still needed on a standby + services, err := getClusterInstanceServices(clientset, cluster) + if err != nil { + return err + } + + // loop over each service to perform the necessary modifications +svcLoop: + for i := range services.Items { + svc := &services.Items[i] + + // loop over the service ports to see if pgBadger port is already set up. if + // it is, we can continue and skip the outerloop + for _, svcPort := range svc.Spec.Ports { + if svcPort.Name == pgBadgerServicePortName { + continue svcLoop + } + } + + // otherwise, we need to append a service port to the list + port, err := strconv.ParseInt( + util.GetValueOrDefault(cluster.Spec.PGBadgerPort, operator.Pgo.Cluster.PGBadgerPort), 10, 32) + // if we can't parse this for whatever reason, issue a warning and continue on + if err != nil { + log.Warn(err) + } + + svcPort := v1.ServicePort{ + Name: pgBadgerServicePortName, + Protocol: v1.ProtocolTCP, + Port: int32(port), + } + + svc.Spec.Ports = append(svc.Spec.Ports, svcPort) + + // if we fail to update the service, warn, but continue on + if _, err := clientset.CoreV1().Services(svc.Namespace).Update(ctx, svc, metav1.UpdateOptions{}); err != nil { + log.Warn(err) + } + } + + return nil +} + +// RemovePGBadger disables the ability for a PostgreSQL cluster to run a +// pgBadger cluster. +// +// This does not modify the Deployment that has the pgBadger sidecar. That is +// handled by the "UpdatePGBadgerSidecar" function, so it can be handled as part +// of a rolling update. +func RemovePGBadger(clientset kubernetes.Interface, restconfig *rest.Config, cluster *crv1.Pgcluster) error { + ctx := context.TODO() + + // close the exporter port on each service + services, err := getClusterInstanceServices(clientset, cluster) + if err != nil { + return err + } + + for i := range services.Items { + svc := &services.Items[i] + svcPorts := []v1.ServicePort{} + + for _, svcPort := range svc.Spec.Ports { + // if we find the service port for the pgBadger, skip it in the loop, but + // as we will not be including it in the update + if svcPort.Name == pgBadgerServicePortName { + continue + } + + svcPorts = append(svcPorts, svcPort) + } + + svc.Spec.Ports = svcPorts + + // if we fail to update the service, warn but continue + if _, err := clientset.CoreV1().Services(svc.Namespace).Update(ctx, svc, metav1.UpdateOptions{}); err != nil { + log.Warn(err) + } + } + return nil +} + +// UpdatePGBadgerSidecar either adds or emoves the pgBadger sidcar from the +// cluster. This is meant to be used as a rolling update callback function +func UpdatePGBadgerSidecar(clientset kubeapi.Interface, cluster *crv1.Pgcluster, deployment *appsv1.Deployment) error { + // need to determine if we are adding or removing + if cluster.Spec.PGBadger { + return addPGBadgerSidecar(cluster, deployment) + } + + removePGBadgerSidecar(deployment) + + return nil +} + +// addPGBadgerSidecar adds the pgBadger sidecar to a Deployment. If pgBadger is +// already present, this call supersedes it and adds the "new version" of the +// pgBadger container. +func addPGBadgerSidecar(cluster *crv1.Pgcluster, deployment *appsv1.Deployment) error { + // use the legacy template generation to make the appropriate substitutions, + // and then get said generation to be placed into an actual Container object + template := operator.GetBadgerAddon(cluster, deployment.Name) + container := v1.Container{} + + if err := json.Unmarshal([]byte(template), &container); err != nil { + return fmt.Errorf("error unmarshalling exporter json into Container: %w ", err) + } + + // append the container to the deployment container list. However, we are + // going to do this carefully, in case the pgBadger container already exists. + // this definition will supersede any exporter container already in the + // containers list + containers := []v1.Container{} + for _, c := range deployment.Spec.Template.Spec.Containers { + // skip if this is the pgBadger container. pgBadger is added after the loop + if c.Name == pgBadgerContainerName { + continue + } + + containers = append(containers, c) + } + + // add the pgBadger container and override the containers list definition + containers = append(containers, container) + deployment.Spec.Template.Spec.Containers = containers + + return nil +} + +// removePGBadgerSidecar removes the pgBadger sidecar from a Deployment. +// +// This involves: +// - Removing the container entry for pgBadger +func removePGBadgerSidecar(deployment *appsv1.Deployment) { + // first, find the container entry in the list of containers and remove it + containers := []v1.Container{} + for _, c := range deployment.Spec.Template.Spec.Containers { + // skip if this is the pgBadger container + if c.Name == pgBadgerContainerName { + continue + } + + containers = append(containers, c) + } + + deployment.Spec.Template.Spec.Containers = containers +} diff --git a/internal/operator/clusterutilities.go b/internal/operator/clusterutilities.go index cfa6351408..da6e37434b 100644 --- a/internal/operator/clusterutilities.go +++ b/internal/operator/clusterutilities.go @@ -330,7 +330,7 @@ func GetBackrestDeployment(clientset kubernetes.Interface, cluster *crv1.Pgclust // GetBadgerAddon is a legacy method that generates a JSONish string to be used // to add a pgBadger sidecar to a PostgreSQL instance -func GetBadgerAddon(clientset kubernetes.Interface, cluster *crv1.Pgcluster, target string) string { +func GetBadgerAddon(cluster *crv1.Pgcluster, target string) string { if !cluster.Spec.PGBadger { return "" } diff --git a/pkg/apiservermsgs/clustermsgs.go b/pkg/apiservermsgs/clustermsgs.go index 53258b36e6..93eb9ffd46 100644 --- a/pkg/apiservermsgs/clustermsgs.go +++ b/pkg/apiservermsgs/clustermsgs.go @@ -372,6 +372,16 @@ const ( UpdateClusterMetricsDisable ) +// UpdateClusterPGBadger determines whether or not to enable/disable the +// pgBadger sidecar in a cluster +type UpdateClusterPGBadger int + +const ( + UpdateClusterPGBadgerDoNothing UpdateClusterPGBadger = iota + UpdateClusterPGBadgerEnable + UpdateClusterPGBadgerDisable +) + // UpdateClusterStandbyStatus defines the types for updating the Standby status type UpdateClusterStandbyStatus int @@ -447,7 +457,10 @@ type UpdateClusterRequest struct { MemoryRequest string // Metrics allows for the enabling/disabling of the metrics sidecar. This can // cause downtime and triggers a rolling update - Metrics UpdateClusterMetrics + Metrics UpdateClusterMetrics + // PGBadger allows for the enabling/disabling of the pgBadger sidecar. This can + // cause downtime and triggers a rolling update + PGBadger UpdateClusterPGBadger Standby UpdateClusterStandbyStatus Startup bool Shutdown bool From a16b1ec32f184ea0af10500a187cc88b1ca3491b Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 29 Dec 2020 10:39:26 -0500 Subject: [PATCH 092/276] Remove "functional labels" that are superseded by CRD attributes The "user labels" that were created for synchronous replication and custom configurations were superseded by the attributes on the pgclusters.crunchydata.com CRD. As such, it is OK to remove these labels. --- .../apiserver/clusterservice/clusterimpl.go | 22 ------------------- internal/config/labels.go | 2 -- internal/operator/clusterutilities.go | 5 ++--- 3 files changed, 2 insertions(+), 27 deletions(-) diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index d93d7f4e4f..81f0fc97b5 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -715,8 +715,6 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. resp.Status.Msg = err.Error() return resp } - // add a label for the custom config - userLabelsMap[config.LABEL_CUSTOM_CONFIG] = request.CustomConfig } if request.ServiceType != "" { @@ -774,10 +772,6 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. log.Debug("userLabelsMap") log.Debugf("%v", userLabelsMap) - if existsGlobalConfig(ns) { - userLabelsMap[config.LABEL_CUSTOM_CONFIG] = config.GLOBAL_CUSTOM_CONFIGMAP - } - if request.StorageConfig != "" && !apiserver.IsValidStorageName(request.StorageConfig) { resp.Status.Code = msgs.Error resp.Status.Msg = fmt.Sprintf("%q storage config was not found", request.StorageConfig) @@ -865,12 +859,6 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. } } - // if synchronous replication has been enabled, then add to user labels - if request.SyncReplication != nil { - userLabelsMap[config.LABEL_SYNC_REPLICATION] = - string(strconv.FormatBool(*request.SyncReplication)) - } - // pgBackRest URI style must be set to either 'path' or 'host'. If it is neither, // log an error and stop the cluster from being created. if request.BackrestS3URIStyle != "" { @@ -1110,10 +1098,6 @@ func getClusterParams(request *msgs.CreateClusterRequest, name string, userLabel }, } - if userLabelsMap[config.LABEL_CUSTOM_CONFIG] != "" { - spec.CustomConfig = userLabelsMap[config.LABEL_CUSTOM_CONFIG] - } - // enable the exporter sidecar based on the what the user passed in or what // the default value is. the user value takes precedence, unless it's false, // as the legacy check only looked for enablement @@ -1643,12 +1627,6 @@ func validateCustomConfig(configmapname, ns string) (bool, error) { return err == nil, err } -func existsGlobalConfig(ns string) bool { - ctx := context.TODO() - _, err := apiserver.Clientset.CoreV1().ConfigMaps(ns).Get(ctx, config.GLOBAL_CUSTOM_CONFIGMAP, metav1.GetOptions{}) - return err == nil -} - func getReplicas(cluster *crv1.Pgcluster, ns string) ([]msgs.ShowClusterReplica, error) { ctx := context.TODO() diff --git a/internal/config/labels.go b/internal/config/labels.go index 5b15db75fb..f9f1176f9a 100644 --- a/internal/config/labels.go +++ b/internal/config/labels.go @@ -46,7 +46,6 @@ const ( LABEL_EXPORTER = "crunchy-postgres-exporter" LABEL_ARCHIVE = "archive" LABEL_ARCHIVE_TIMEOUT = "archive-timeout" - LABEL_CUSTOM_CONFIG = "custom-config" LABEL_NODE_LABEL_KEY = "NodeLabelKey" LABEL_NODE_LABEL_VALUE = "NodeLabelValue" LABEL_REPLICA_NAME = "replica-name" @@ -55,7 +54,6 @@ const ( LABEL_IMAGE_PREFIX = "image-prefix" LABEL_SERVICE_TYPE = "service-type" LABEL_POD_ANTI_AFFINITY = "pg-pod-anti-affinity" - LABEL_SYNC_REPLICATION = "sync-replication" ) const ( diff --git a/internal/operator/clusterutilities.go b/internal/operator/clusterutilities.go index da6e37434b..1095fbb2b2 100644 --- a/internal/operator/clusterutilities.go +++ b/internal/operator/clusterutilities.go @@ -963,10 +963,9 @@ func GetSyncReplication(specSyncReplication *bool) bool { // alawys use the value from the CR if explicitly provided if specSyncReplication != nil { return *specSyncReplication - } else if Pgo.Cluster.SyncReplication { - return true } - return false + + return Pgo.Cluster.SyncReplication } // GetTolerations returns any tolerations that may be defined in a tolerations From d2efb2f9075e976b41f14c5107e775a8e7f95afe Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 29 Dec 2020 11:43:35 -0500 Subject: [PATCH 093/276] Move ServiceType control to CRD attribute This adds a "ServiceType" parameter to both the pgclusters + pgreplicas custom resource definitions so that the behavior can be managed withou the user of a nested label. --- cmd/pgo/cmd/cluster.go | 2 +- cmd/pgo/cmd/scale.go | 3 +- docs/content/custom-resources/_index.md | 1 + .../apiserver/clusterservice/clusterimpl.go | 19 ++++--- .../apiserver/clusterservice/scaleimpl.go | 20 ++++--- internal/config/labels.go | 1 - internal/config/pgoconfig.go | 23 +++----- internal/operator/cluster/cluster.go | 2 +- internal/operator/cluster/clusterlogic.go | 57 ++++++++++++------- internal/operator/cluster/upgrade.go | 7 +++ pkg/apis/crunchydata.com/v1/cluster.go | 4 ++ pkg/apis/crunchydata.com/v1/replica.go | 15 +++-- pkg/apiservermsgs/clustermsgs.go | 4 +- 13 files changed, 96 insertions(+), 62 deletions(-) diff --git a/cmd/pgo/cmd/cluster.go b/cmd/pgo/cmd/cluster.go index 0e9d2b739c..fd2f1241ab 100644 --- a/cmd/pgo/cmd/cluster.go +++ b/cmd/pgo/cmd/cluster.go @@ -278,7 +278,7 @@ func createCluster(args []string, ns string, createClusterCmd *cobra.Command) { r.ExporterMemoryRequest = ExporterMemoryRequest r.ExporterMemoryLimit = ExporterMemoryLimit r.BadgerFlag = BadgerFlag - r.ServiceType = ServiceType + r.ServiceType = v1.ServiceType(ServiceType) r.AutofailFlag = !DisableAutofailFlag r.PgbouncerFlag = PgbouncerFlag r.BackrestStorageConfig = BackrestStorageConfig diff --git a/cmd/pgo/cmd/scale.go b/cmd/pgo/cmd/scale.go index 6352e91cd3..5303c6dd6a 100644 --- a/cmd/pgo/cmd/scale.go +++ b/cmd/pgo/cmd/scale.go @@ -24,6 +24,7 @@ import ( msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" + v1 "k8s.io/api/core/v1" ) var ReplicaCount int @@ -76,7 +77,7 @@ func scaleCluster(args []string, ns string) { Namespace: ns, NodeLabel: NodeLabel, ReplicaCount: ReplicaCount, - ServiceType: ServiceType, + ServiceType: v1.ServiceType(ServiceType), StorageConfig: StorageConfig, Tolerations: getClusterTolerations(Tolerations), } diff --git a/docs/content/custom-resources/_index.md b/docs/content/custom-resources/_index.md index d70831814b..61204b26e5 100644 --- a/docs/content/custom-resources/_index.md +++ b/docs/content/custom-resources/_index.md @@ -767,6 +767,7 @@ make changes, as described below. | ReplicaStorage | `create` | A specification that gives information about the storage attributes for any replicas in the PostgreSQL cluster. For details, please see the `Storage Specification` section below. This will likely be changed in the future based on the nature of the high-availability system, but presently it is still required that you set it. It is recommended you use similar settings to that of `PrimaryStorage`. | | Replicas | `create` | The number of replicas to create after a PostgreSQL primary is first initialized. This only works on create; to scale a cluster after it is initialized, please use the [`pgo scale`]({{< relref "/pgo-client/reference/pgo_scale.md" >}}) command. | | Resources | `create`, `update` | Specify the container resource requests that the PostgreSQL cluster should use. Follows the [Kubernetes definitions of resource requests](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | +| ServiceType | `create` | Sets the Kubernetes [Service](https://kubernetes.io/docs/concepts/services-networking/service/) type to use for the cluster. If not set, defaults to `ClusterIP`. | | SyncReplication | `create` | If set to `true`, specifies the PostgreSQL cluster to use [synchronous replication]({{< relref "/architecture/high-availability/_index.md#how-the-crunchy-postgresql-operator-uses-pod-anti-affinity#synchronous-replication-guarding-against-transactions-loss" >}}).| | User | `create` | The name of the PostgreSQL user that is created when the PostgreSQL cluster is first created. | | UserLabels | `create` | A set of key-value string pairs that are used as a sort of "catch-all" for things that really should be modeled in the CRD. These values do get copied to the actually CR labels. If you want to set up metrics collection or pgBadger, you would specify `"crunchy-postgres-exporter": "true"` and `"crunchy-pgbadger": "true"` here, respectively. However, this structure does need to be set, so just follow whatever is in the example. | diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index 81f0fc97b5..aa86db62f1 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -717,14 +717,14 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. } } - if request.ServiceType != "" { - if request.ServiceType != config.DEFAULT_SERVICE_TYPE && request.ServiceType != config.LOAD_BALANCER_SERVICE_TYPE && request.ServiceType != config.NODEPORT_SERVICE_TYPE { - resp.Status.Code = msgs.Error - resp.Status.Msg = "error ServiceType should be either ClusterIP or LoadBalancer " - - return resp - } - userLabelsMap[config.LABEL_SERVICE_TYPE] = request.ServiceType + // validate the optional ServiceType parameter + switch request.ServiceType { + default: + resp.Status.Code = msgs.Error + resp.Status.Msg = fmt.Sprintf("invalid service type %q", request.ServiceType) + return resp + case v1.ServiceTypeClusterIP, v1.ServiceTypeNodePort, + v1.ServiceTypeLoadBalancer, v1.ServiceTypeExternalName, "": // no-op } // if the request is for a standby cluster then validate it to ensure all parameters have @@ -1359,6 +1359,9 @@ func getClusterParams(request *msgs.CreateClusterRequest, name string, userLabel spec.Replicas = strconv.Itoa(request.ReplicaCount) log.Debugf("replicas is %s", spec.Replicas) } + + spec.ServiceType = request.ServiceType + spec.UserLabels = userLabelsMap spec.UserLabels[config.LABEL_PGO_VERSION] = msgs.PGO_VERSION diff --git a/internal/apiserver/clusterservice/scaleimpl.go b/internal/apiserver/clusterservice/scaleimpl.go index 7713f33eb0..0a8bcf0fff 100644 --- a/internal/apiserver/clusterservice/scaleimpl.go +++ b/internal/apiserver/clusterservice/scaleimpl.go @@ -27,6 +27,7 @@ import ( msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" + v1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -85,15 +86,16 @@ func ScaleCluster(request msgs.ClusterScaleRequest, pgouser string) msgs.Cluster if request.CCPImageTag != "" { spec.UserLabels[config.LABEL_CCP_IMAGE_TAG_KEY] = request.CCPImageTag } - if request.ServiceType != "" { - if request.ServiceType != config.DEFAULT_SERVICE_TYPE && - request.ServiceType != config.NODEPORT_SERVICE_TYPE && - request.ServiceType != config.LOAD_BALANCER_SERVICE_TYPE { - response.Status.Code = msgs.Error - response.Status.Msg = "error --service-type should be either ClusterIP, NodePort, or LoadBalancer " - return response - } - spec.UserLabels[config.LABEL_SERVICE_TYPE] = request.ServiceType + + // check the optional ServiceType paramater + switch request.ServiceType { + default: + response.Status.Code = msgs.Error + response.Status.Msg = fmt.Sprintf("invalid service type %q", request.ServiceType) + return response + case v1.ServiceTypeClusterIP, v1.ServiceTypeNodePort, + v1.ServiceTypeLoadBalancer, v1.ServiceTypeExternalName, "": + spec.ServiceType = request.ServiceType } // set replica node lables to blank to start with, then check for overrides diff --git a/internal/config/labels.go b/internal/config/labels.go index f9f1176f9a..30bce70cdf 100644 --- a/internal/config/labels.go +++ b/internal/config/labels.go @@ -52,7 +52,6 @@ const ( LABEL_CCP_IMAGE_TAG_KEY = "ccp-image-tag" LABEL_CCP_IMAGE_KEY = "ccp-image" LABEL_IMAGE_PREFIX = "image-prefix" - LABEL_SERVICE_TYPE = "service-type" LABEL_POD_ANTI_AFFINITY = "pg-pod-anti-affinity" ) diff --git a/internal/config/pgoconfig.go b/internal/config/pgoconfig.go index 6c870686a0..2a72513437 100644 --- a/internal/config/pgoconfig.go +++ b/internal/config/pgoconfig.go @@ -205,7 +205,7 @@ type ClusterStruct struct { PasswordAgeDays string PasswordLength string Replicas string - ServiceType string + ServiceType v1.ServiceType BackrestPort int BackrestS3Bucket string BackrestS3Endpoint string @@ -262,10 +262,8 @@ type PgoConfig struct { } const ( - DEFAULT_SERVICE_TYPE = "ClusterIP" - LOAD_BALANCER_SERVICE_TYPE = "LoadBalancer" - NODEPORT_SERVICE_TYPE = "NodePort" - CONFIG_PATH = "pgo.yaml" + DefaultServiceType = v1.ServiceTypeClusterIP + CONFIG_PATH = "pgo.yaml" ) const ( @@ -345,15 +343,12 @@ func (c *PgoConfig) Validate() error { return errors.New(errPrefix + "Pgo.PGOImageTag is required") } - if c.Cluster.ServiceType == "" { - log.Warn("Cluster.ServiceType not set, using default, ClusterIP ") - c.Cluster.ServiceType = DEFAULT_SERVICE_TYPE - } else { - if c.Cluster.ServiceType != DEFAULT_SERVICE_TYPE && - c.Cluster.ServiceType != LOAD_BALANCER_SERVICE_TYPE && - c.Cluster.ServiceType != NODEPORT_SERVICE_TYPE { - return errors.New(errPrefix + "Cluster.ServiceType is required to be either ClusterIP, NodePort, or LoadBalancer") - } + // if ServiceType is set, ensure it is valid + switch c.Cluster.ServiceType { + default: + return fmt.Errorf("Cluster.ServiceType is an invalid ServiceType: %q", c.Cluster.ServiceType) + case v1.ServiceTypeClusterIP, v1.ServiceTypeNodePort, + v1.ServiceTypeLoadBalancer, v1.ServiceTypeExternalName, "": // no-op } if c.Cluster.CCPImagePrefix == "" { diff --git a/internal/operator/cluster/cluster.go b/internal/operator/cluster/cluster.go index 2b292290c5..9023fe080f 100644 --- a/internal/operator/cluster/cluster.go +++ b/internal/operator/cluster/cluster.go @@ -53,7 +53,7 @@ type ServiceTemplateFields struct { Port string PGBadgerPort string ExporterPort string - ServiceType string + ServiceType v1.ServiceType } // ReplicaSuffix ... diff --git a/internal/operator/cluster/clusterlogic.go b/internal/operator/cluster/clusterlogic.go index f6ed1c52f8..86ea813208 100644 --- a/internal/operator/cluster/clusterlogic.go +++ b/internal/operator/cluster/clusterlogic.go @@ -49,29 +49,37 @@ import ( // addClusterCreateMissingService creates a service for the cluster primary if // it does not yet exist. -func addClusterCreateMissingService(clientset kubernetes.Interface, cl *crv1.Pgcluster, namespace string) error { - st := operator.Pgo.Cluster.ServiceType - if cl.Spec.UserLabels[config.LABEL_SERVICE_TYPE] != "" { - st = cl.Spec.UserLabels[config.LABEL_SERVICE_TYPE] +func addClusterCreateMissingService(clientset kubernetes.Interface, cluster *crv1.Pgcluster, namespace string) error { + // start with the default value for ServiceType + serviceType := config.DefaultServiceType + + // then see if there is a configuration provided value + if operator.Pgo.Cluster.ServiceType != "" { + serviceType = operator.Pgo.Cluster.ServiceType + } + + // then see if there is an override on the custom resource definition + if cluster.Spec.ServiceType != "" { + serviceType = cluster.Spec.ServiceType } // create the primary service serviceFields := ServiceTemplateFields{ - Name: cl.Spec.Name, - ServiceName: cl.Spec.Name, - ClusterName: cl.Spec.Name, - Port: cl.Spec.Port, - ServiceType: st, + Name: cluster.Spec.Name, + ServiceName: cluster.Spec.Name, + ClusterName: cluster.Spec.Name, + Port: cluster.Spec.Port, + ServiceType: serviceType, } // set the pgBadger port if pgBadger is enabled - if cl.Spec.PGBadger { - serviceFields.PGBadgerPort = cl.Spec.PGBadgerPort + if cluster.Spec.PGBadger { + serviceFields.PGBadgerPort = cluster.Spec.PGBadgerPort } // set the exporter port if exporter is enabled - if cl.Spec.Exporter { - serviceFields.ExporterPort = cl.Spec.ExporterPort + if cluster.Spec.Exporter { + serviceFields.ExporterPort = cluster.Spec.ExporterPort } return CreateService(clientset, &serviceFields, namespace) @@ -369,11 +377,22 @@ func DeleteCluster(clientset kubernetes.Interface, cl *crv1.Pgcluster, namespace // scaleReplicaCreateMissingService creates a service for cluster replicas if // it does not yet exist. func scaleReplicaCreateMissingService(clientset kubernetes.Interface, replica *crv1.Pgreplica, cluster *crv1.Pgcluster, namespace string) error { - st := operator.Pgo.Cluster.ServiceType - if replica.Spec.UserLabels[config.LABEL_SERVICE_TYPE] != "" { - st = replica.Spec.UserLabels[config.LABEL_SERVICE_TYPE] - } else if cluster.Spec.UserLabels[config.LABEL_SERVICE_TYPE] != "" { - st = cluster.Spec.UserLabels[config.LABEL_SERVICE_TYPE] + // start with the default value for ServiceType + serviceType := config.DefaultServiceType + + // then see if there is a configuration provided value + if operator.Pgo.Cluster.ServiceType != "" { + serviceType = operator.Pgo.Cluster.ServiceType + } + + // then see if there is an override on the custom resource definition + if cluster.Spec.ServiceType != "" { + serviceType = cluster.Spec.ServiceType + } + + // and finally, see if there is an instance specific override. Yay. + if replica.Spec.ServiceType != "" { + serviceType = replica.Spec.ServiceType } serviceName := fmt.Sprintf("%s-replica", replica.Spec.ClusterName) @@ -382,7 +401,7 @@ func scaleReplicaCreateMissingService(clientset kubernetes.Interface, replica *c ServiceName: serviceName, ClusterName: replica.Spec.ClusterName, Port: cluster.Spec.Port, - ServiceType: st, + ServiceType: serviceType, } // only add references to the exporter / pgBadger ports diff --git a/internal/operator/cluster/upgrade.go b/internal/operator/cluster/upgrade.go index f46ed64671..729a77e58f 100644 --- a/internal/operator/cluster/upgrade.go +++ b/internal/operator/cluster/upgrade.go @@ -490,6 +490,13 @@ func preparePgclusterForUpgrade(pgcluster *crv1.Pgcluster, parameters map[string delete(pgcluster.ObjectMeta.Labels, "crunchy-pgbadger") } + // 4.6.0 moved the format "service-type" label into the ServiceType CRD + // attribute, so we may need to do the same + if val, ok := pgcluster.Spec.UserLabels["service-type"]; ok { + pgcluster.Spec.ServiceType = v1.ServiceType(val) + delete(pgcluster.Spec.UserLabels, "service-type") + } + // since the current primary label is not used in this version of the Postgres Operator, // delete it before moving on to other upgrade tasks delete(pgcluster.ObjectMeta.Labels, config.LABEL_CURRENT_PRIMARY) diff --git a/pkg/apis/crunchydata.com/v1/cluster.go b/pkg/apis/crunchydata.com/v1/cluster.go index 72b26cbc0e..2347cfb637 100644 --- a/pkg/apis/crunchydata.com/v1/cluster.go +++ b/pkg/apis/crunchydata.com/v1/cluster.go @@ -132,6 +132,10 @@ type PgclusterSpec struct { // annotations that are propagated to all managed Deployments Annotations ClusterAnnotations `json:"annotations"` + // ServiceType references the type of Service that should be used when + // deploying PostgreSQL instances + ServiceType v1.ServiceType `json:"serviceType"` + // Tolerations are an optional list of Pod toleration rules that are applied // to the PostgreSQL instance. Tolerations []v1.Toleration `json:"tolerations"` diff --git a/pkg/apis/crunchydata.com/v1/replica.go b/pkg/apis/crunchydata.com/v1/replica.go index 1bfba208fe..45f6cd123a 100644 --- a/pkg/apis/crunchydata.com/v1/replica.go +++ b/pkg/apis/crunchydata.com/v1/replica.go @@ -37,12 +37,15 @@ type Pgreplica struct { // PgreplicaSpec ... // swagger:ignore type PgreplicaSpec struct { - Namespace string `json:"namespace"` - Name string `json:"name"` - ClusterName string `json:"clustername"` - ReplicaStorage PgStorageSpec `json:"replicastorage"` - Status string `json:"status"` - UserLabels map[string]string `json:"userlabels"` + Namespace string `json:"namespace"` + Name string `json:"name"` + ClusterName string `json:"clustername"` + ReplicaStorage PgStorageSpec `json:"replicastorage"` + // ServiceType references the type of Service that should be used when + // deploying PostgreSQL instances + ServiceType v1.ServiceType `json:"serviceType"` + Status string `json:"status"` + UserLabels map[string]string `json:"userlabels"` // Tolerations are an optional list of Pod toleration rules that are applied // to the PostgreSQL instance. Tolerations []v1.Toleration `json:"tolerations"` diff --git a/pkg/apiservermsgs/clustermsgs.go b/pkg/apiservermsgs/clustermsgs.go index 93eb9ffd46..61255f6cc8 100644 --- a/pkg/apiservermsgs/clustermsgs.go +++ b/pkg/apiservermsgs/clustermsgs.go @@ -62,7 +62,7 @@ type CreateClusterRequest struct { CCPImagePrefix string PGOImagePrefix string ReplicaCount int - ServiceType string + ServiceType v1.ServiceType MetricsFlag bool // ExporterCPULimit, if specified, is the value of the max CPU for a // Crunchy Postgres Exporter sidecar container @@ -572,7 +572,7 @@ type ClusterScaleRequest struct { ReplicaCount int `json:"replicaCount"` // ServiceType is the kind of Service to deploy with this instance. Defaults // to the value on the cluster. - ServiceType string `json:"serviceType"` + ServiceType v1.ServiceType `json:"serviceType"` // StorageConfig, if provided, specifies which of the storage configuration // options should be used. Defaults to what the main cluster definition uses. StorageConfig string `json:"storageConfig"` From db427b4c77e72cbe59b6eb6703d89cc24bea5327 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 29 Dec 2020 15:34:59 -0500 Subject: [PATCH 094/276] Allow for ServiceType to be updated on existing clusters This allows for updates to the ServiceType attribute to propagate to the managed PostgreSQL Services. Any updates follow this level of precedence, from least to most: - Operator default (ClusterIP) - Configuration default - Cluster => ServiceType - Replica => ServiceType --- docs/content/custom-resources/_index.md | 2 +- .../pgcluster/pgclustercontroller.go | 58 ++++++++++++++++ .../pgreplica/pgreplicacontroller.go | 8 +++ internal/operator/cluster/cluster.go | 2 +- internal/operator/cluster/service.go | 66 +++++++++++++++++++ 5 files changed, 134 insertions(+), 2 deletions(-) diff --git a/docs/content/custom-resources/_index.md b/docs/content/custom-resources/_index.md index 61204b26e5..212791e7fd 100644 --- a/docs/content/custom-resources/_index.md +++ b/docs/content/custom-resources/_index.md @@ -767,7 +767,7 @@ make changes, as described below. | ReplicaStorage | `create` | A specification that gives information about the storage attributes for any replicas in the PostgreSQL cluster. For details, please see the `Storage Specification` section below. This will likely be changed in the future based on the nature of the high-availability system, but presently it is still required that you set it. It is recommended you use similar settings to that of `PrimaryStorage`. | | Replicas | `create` | The number of replicas to create after a PostgreSQL primary is first initialized. This only works on create; to scale a cluster after it is initialized, please use the [`pgo scale`]({{< relref "/pgo-client/reference/pgo_scale.md" >}}) command. | | Resources | `create`, `update` | Specify the container resource requests that the PostgreSQL cluster should use. Follows the [Kubernetes definitions of resource requests](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | -| ServiceType | `create` | Sets the Kubernetes [Service](https://kubernetes.io/docs/concepts/services-networking/service/) type to use for the cluster. If not set, defaults to `ClusterIP`. | +| ServiceType | `create`, `update` | Sets the Kubernetes [Service](https://kubernetes.io/docs/concepts/services-networking/service/) type to use for the cluster. If not set, defaults to `ClusterIP`. | | SyncReplication | `create` | If set to `true`, specifies the PostgreSQL cluster to use [synchronous replication]({{< relref "/architecture/high-availability/_index.md#how-the-crunchy-postgresql-operator-uses-pod-anti-affinity#synchronous-replication-guarding-against-transactions-loss" >}}).| | User | `create` | The name of the PostgreSQL user that is created when the PostgreSQL cluster is first created. | | UserLabels | `create` | A set of key-value string pairs that are used as a sort of "catch-all" for things that really should be modeled in the CRD. These values do get copied to the actually CR labels. If you want to set up metrics collection or pgBadger, you would specify `"crunchy-postgres-exporter": "true"` and `"crunchy-pgbadger": "true"` here, respectively. However, this structure does need to be set, so just follow whatever is in the example. | diff --git a/internal/controller/pgcluster/pgclustercontroller.go b/internal/controller/pgcluster/pgclustercontroller.go index d2c910ad99..c4d74cf009 100644 --- a/internal/controller/pgcluster/pgclustercontroller.go +++ b/internal/controller/pgcluster/pgclustercontroller.go @@ -37,6 +37,7 @@ import ( appsv1 "k8s.io/api/apps/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" @@ -236,6 +237,12 @@ func (c *Controller) onUpdate(oldObj, newObj interface{}) { } } + // if the service type has changed, update the service type. Log an error if + // it fails, but continue on + if oldcluster.Spec.ServiceType != newcluster.Spec.ServiceType { + updateServices(c.Client, newcluster) + } + // see if we are adding / removing the metrics collection sidecar if oldcluster.Spec.Exporter != newcluster.Spec.Exporter { var err error @@ -483,6 +490,57 @@ func updatePgBouncer(c *Controller, oldCluster *crv1.Pgcluster, newCluster *crv1 return clusteroperator.UpdatePgbouncer(c.Client, oldCluster, newCluster) } +// updateServices handles any updates to the Service objects. Given how legacy +// replica services are handled (really, replica service singular), the update +// around replica services is a bit grotty, but it is what it is. +// +// If there are errors on the updates, this logs them but will continue on +// unless otherwise noted. +func updateServices(clientset kubeapi.Interface, cluster *crv1.Pgcluster) { + ctx := context.TODO() + + // handle the primary instance + if err := clusteroperator.UpdateClusterService(clientset, cluster); err != nil { + log.Error(err) + } + + // handle the replica instances. Ish. This is kind of "broken" due to the + // fact that we have a single service for all of the replicas. so, we'll + // loop through all of the replicas and try to see if any of them have + // any specialized service types. If so, we'll pluck that one out and use + // it to apply + options := metav1.ListOptions{ + LabelSelector: fields.OneTermEqualSelector(config.LABEL_PG_CLUSTER, cluster.Name).String(), + } + replicas, err := clientset.CrunchydataV1().Pgreplicas(cluster.Namespace).List(ctx, options) + + // well, if there is an error here, log it and abort + if err != nil { + log.Error(err) + return + } + + // if there are no replicas, also return + if len(replicas.Items) == 0 { + return + } + + // ok, we're guaranteed at least one replica, so there should be a Service + var replica *crv1.Pgreplica + for i := range replicas.Items { + // store the replica no matter what, for later comparison + replica = &replicas.Items[i] + // however, if the servicetype is customized, break out. Yup. + if replica.Spec.ServiceType != "" { + break + } + } + + if err := clusteroperator.UpdateReplicaService(clientset, cluster, replica); err != nil { + log.Error(err) + } +} + // updateTablespaces updates the PostgreSQL instance Deployments to reflect the // new PostgreSQL tablespaces that should be added func updateTablespaces(c *Controller, oldCluster *crv1.Pgcluster, newCluster *crv1.Pgcluster) error { diff --git a/internal/controller/pgreplica/pgreplicacontroller.go b/internal/controller/pgreplica/pgreplicacontroller.go index 79f8538100..c37ee16b58 100644 --- a/internal/controller/pgreplica/pgreplicacontroller.go +++ b/internal/controller/pgreplica/pgreplicacontroller.go @@ -212,6 +212,14 @@ func (c *Controller) onUpdate(oldObj, newObj interface{}) { } } + // if the service type changed, updated on the instance + // if there is an error, log but continue + if oldPgreplica.Spec.ServiceType != newPgreplica.Spec.ServiceType { + if err := clusteroperator.UpdateReplicaService(c.Client, cluster, newPgreplica); err != nil { + log.Error(err) + } + } + // if the tolerations array changed, updated the tolerations on the instance if !reflect.DeepEqual(oldPgreplica.Spec.Tolerations, newPgreplica.Spec.Tolerations) { // get the Deployment object associated with this instance diff --git a/internal/operator/cluster/cluster.go b/internal/operator/cluster/cluster.go index 9023fe080f..b16a89829b 100644 --- a/internal/operator/cluster/cluster.go +++ b/internal/operator/cluster/cluster.go @@ -56,7 +56,7 @@ type ServiceTemplateFields struct { ServiceType v1.ServiceType } -// ReplicaSuffix ... +// ReplicaSuffix is the suffix of the replica Service name const ReplicaSuffix = "-replica" const ( diff --git a/internal/operator/cluster/service.go b/internal/operator/cluster/service.go index bca0eb9e45..73edd7e35e 100644 --- a/internal/operator/cluster/service.go +++ b/internal/operator/cluster/service.go @@ -26,12 +26,22 @@ import ( "github.com/crunchydata/postgres-operator/internal/config" "github.com/crunchydata/postgres-operator/internal/operator" + crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" log "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" ) +// serviceInfo is a structured way of compiling all of the info required to +// update a service +type serviceInfo struct { + serviceName string + serviceNamespace string + serviceType v1.ServiceType +} + // CreateService ... func CreateService(clientset kubernetes.Interface, fields *ServiceTemplateFields, namespace string) error { ctx := context.TODO() @@ -63,3 +73,59 @@ func CreateService(clientset kubernetes.Interface, fields *ServiceTemplateFields return err } + +// UpdateClusterService updates parameters (really just one) on a Service that +// represents a PostgreSQL cluster +func UpdateClusterService(clientset kubernetes.Interface, cluster *crv1.Pgcluster) error { + return updateService(clientset, serviceInfo{ + serviceName: cluster.Name, + serviceNamespace: cluster.Namespace, + serviceType: cluster.Spec.ServiceType, + }) +} + +// UpdateClusterService updates parameters (really just one) on a Service that +// represents a PostgreSQL replca instance +func UpdateReplicaService(clientset kubernetes.Interface, cluster *crv1.Pgcluster, replica *crv1.Pgreplica) error { + serviceType := cluster.Spec.ServiceType + + // if the replica has a specific service type, override with that + if replica.Spec.ServiceType != "" { + serviceType = replica.Spec.ServiceType + } + + return updateService(clientset, serviceInfo{ + serviceName: replica.Spec.ClusterName + ReplicaSuffix, + serviceNamespace: replica.Namespace, + serviceType: serviceType, + }) +} + +// updateService does the legwork for updating a service +func updateService(clientset kubernetes.Interface, info serviceInfo) error { + ctx := context.TODO() + + // first, attempt to get the Service. If we cannot do that, then we can't + // update the service + svc, err := clientset.CoreV1().Services(info.serviceNamespace).Get(ctx, info.serviceName, metav1.GetOptions{}) + if err != nil { + return err + } + + // update the desired attributes, which is really just the ServiceType + svc.Spec.Type = info.serviceType + + // ...so, while the documentation says that any "NodePort" settings are wiped + // if the type is not "NodePort", this is actually not the case, so we need to + // overcompensate for that + // Ref: https://godoc.org/k8s.io/api/core/v1#ServicePort + if svc.Spec.Type != v1.ServiceTypeNodePort { + for i := range svc.Spec.Ports { + svc.Spec.Ports[i].NodePort = 0 + } + } + + _, err = clientset.CoreV1().Services(info.serviceNamespace).Update(ctx, svc, metav1.UpdateOptions{}) + + return err +} From c90f4e13c0f9ad1120dc39c18840b13f8215f99c Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 29 Dec 2020 17:17:45 -0500 Subject: [PATCH 095/276] Move autofail toggle to `DisableAutofail` attribute on CRD This required relatively little shifting of code, as this former label effectively behaved like an attribute. Given autofail (read: HA) is the default in the PostgreSQL Operator, the chosen nomenclature is to reflect that we want the use to make a conscious decision to disable HA (given Go defaults booleans to "false"). --- docs/content/custom-resources/_index.md | 1 + .../apiserver/clusterservice/clusterimpl.go | 11 +++----- internal/config/labels.go | 1 - .../pgcluster/pgclustercontroller.go | 27 ++++++------------- internal/controller/pod/inithandler.go | 18 ++++++------- internal/operator/cluster/upgrade.go | 15 ++++++++--- internal/util/cluster.go | 10 ------- pkg/apis/crunchydata.com/v1/cluster.go | 4 +++ 8 files changed, 35 insertions(+), 52 deletions(-) diff --git a/docs/content/custom-resources/_index.md b/docs/content/custom-resources/_index.md index 212791e7fd..8683722431 100644 --- a/docs/content/custom-resources/_index.md +++ b/docs/content/custom-resources/_index.md @@ -749,6 +749,7 @@ make changes, as described below. | ClusterName | `create` | The name of the PostgreSQL cluster, e.g. `hippo`. This is used to group PostgreSQL instances (primary, replicas) together. | | CustomConfig | `create` | If specified, references a custom ConfigMap to use when bootstrapping a PostgreSQL cluster. For the shape of this file, please see the section on [Custom Configuration]({{< relref "/advanced/custom-configuration.md" >}}) | | Database | `create` | The name of a database that the PostgreSQL user can log into after the PostgreSQL cluster is created. | +| DisableAutofail | `create`, `update` | If set to true, disables the high availability capabilities of a PostgreSQL cluster. By default, every cluster can have high availability if there is at least one replica. | | ExporterLimits | `create`, `update` | Specify the container resource limits that the `crunchy-postgres-exporter` sidecar uses when it is deployed with a PostgreSQL instance. Follows the [Kubernetes definitions of resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | | Exporter | `create`,`update` | If `true`, deploys the `crunchy-postgres-exporter` sidecar for metrics collection | | ExporterPort | `create` | If `Exporter` is `true`, then this specifies the port that the metrics sidecar runs on (e.g. `9187`) | diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index aa86db62f1..56f228bfa6 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -1472,12 +1472,7 @@ func getClusterParams(request *msgs.CreateClusterRequest, name string, userLabel labels := make(map[string]string) labels[config.LABEL_NAME] = name - if !request.AutofailFlag || apiserver.Pgo.Cluster.DisableAutofail { - labels[config.LABEL_AUTOFAIL] = "false" - } else { - labels[config.LABEL_AUTOFAIL] = "true" - } - + spec.DisableAutofail = !request.AutofailFlag || apiserver.Pgo.Cluster.DisableAutofail // set whether or not the cluster will be a standby cluster spec.Standby = request.Standby // set the pgBackRest repository path @@ -1869,9 +1864,9 @@ func UpdateCluster(request *msgs.UpdateClusterRequest) msgs.UpdateClusterRespons // Make the change based on the value of Autofail vis-a-vis UpdateClusterAutofailStatus switch request.Autofail { case msgs.UpdateClusterAutofailEnable: - cluster.ObjectMeta.Labels[config.LABEL_AUTOFAIL] = "true" + cluster.Spec.DisableAutofail = false case msgs.UpdateClusterAutofailDisable: - cluster.ObjectMeta.Labels[config.LABEL_AUTOFAIL] = "false" + cluster.Spec.DisableAutofail = true case msgs.UpdateClusterAutofailDoNothing: // no-op } diff --git a/internal/config/labels.go b/internal/config/labels.go index 30bce70cdf..1d0f9b7c3f 100644 --- a/internal/config/labels.go +++ b/internal/config/labels.go @@ -28,7 +28,6 @@ const ( const LABEL_PGTASK = "pg-task" const ( - LABEL_AUTOFAIL = "autofail" LABEL_FAILOVER = "failover" LABEL_RESTART = "restart" ) diff --git a/internal/controller/pgcluster/pgclustercontroller.go b/internal/controller/pgcluster/pgclustercontroller.go index c4d74cf009..8875ec481a 100644 --- a/internal/controller/pgcluster/pgclustercontroller.go +++ b/internal/controller/pgcluster/pgclustercontroller.go @@ -20,7 +20,6 @@ import ( "encoding/json" "io/ioutil" "reflect" - "strconv" "strings" "github.com/crunchydata/postgres-operator/internal/config" @@ -202,26 +201,16 @@ func (c *Controller) onUpdate(oldObj, newObj interface{}) { _ = clusteroperator.StartupCluster(c.Client, *newcluster) } - // check to see if the "autofail" label on the pgcluster CR has been changed from either true to false, or from - // false to true. If it has been changed to false, autofail will then be disabled in the pg cluster. If has - // been changed to true, autofail will then be enabled in the pg cluster - if newcluster.ObjectMeta.Labels[config.LABEL_AUTOFAIL] != "" { - autofailEnabledOld, err := strconv.ParseBool(oldcluster.ObjectMeta.Labels[config.LABEL_AUTOFAIL]) - if err != nil { - log.Error(err) - return - } - autofailEnabledNew, err := strconv.ParseBool(newcluster.ObjectMeta.Labels[config.LABEL_AUTOFAIL]) - if err != nil { + // check to see if autofail setting has been changed. If set to "true", it + // will be disabled, otherwise it will be enabled. Simple. + if oldcluster.Spec.DisableAutofail != newcluster.Spec.DisableAutofail { + // take the inverse, as this func checks for autofail being enabled + // if we can't toggle autofailover, log the error but continue on + if err := util.ToggleAutoFailover(c.Client, !newcluster.Spec.DisableAutofail, + newcluster.ObjectMeta.Labels[config.LABEL_PGHA_SCOPE], + newcluster.ObjectMeta.Namespace); err != nil { log.Error(err) - return } - if autofailEnabledNew != autofailEnabledOld { - _ = util.ToggleAutoFailover(c.Client, autofailEnabledNew, - newcluster.ObjectMeta.Labels[config.LABEL_PGHA_SCOPE], - newcluster.ObjectMeta.Namespace) - } - } // handle standby being enabled and disabled for the cluster diff --git a/internal/controller/pod/inithandler.go b/internal/controller/pod/inithandler.go index 2f09dbef3c..733d301cb7 100644 --- a/internal/controller/pod/inithandler.go +++ b/internal/controller/pod/inithandler.go @@ -18,7 +18,6 @@ limitations under the License. import ( "context" "fmt" - "strconv" "github.com/crunchydata/postgres-operator/internal/config" "github.com/crunchydata/postgres-operator/internal/controller" @@ -101,15 +100,14 @@ func (c *Controller) handleBackRestRepoInit(newPod *apiv1.Pod, cluster *crv1.Pgc // regardless of the specific type of cluster (e.g. regualar or standby) or the reason the // cluster is being initialized (initial bootstrap or restore) func (c *Controller) handleCommonInit(cluster *crv1.Pgcluster) error { - // Disable autofailover in the cluster that is now "Ready" if the autofail label is set - // to "false" on the pgcluster (i.e. label "autofail=true") - autofailEnabled, err := strconv.ParseBool(cluster.ObjectMeta.Labels[config.LABEL_AUTOFAIL]) - if err != nil { - log.Error(err) - return err - } else if !autofailEnabled { - _ = util.ToggleAutoFailover(c.Client, false, - cluster.ObjectMeta.Labels[config.LABEL_PGHA_SCOPE], cluster.Namespace) + // Disable autofailover in the cluster that is now "Ready" if autofilover + // is disabled for the cluster + if cluster.Spec.DisableAutofail { + // accepts the inverse + if err := util.ToggleAutoFailover(c.Client, !cluster.Spec.DisableAutofail, + cluster.ObjectMeta.Labels[config.LABEL_PGHA_SCOPE], cluster.Namespace); err != nil { + log.Error(err) + } } if err := operator.UpdatePGHAConfigInitFlag(c.Client, false, cluster.Name, diff --git a/internal/operator/cluster/upgrade.go b/internal/operator/cluster/upgrade.go index 729a77e58f..639f46ae41 100644 --- a/internal/operator/cluster/upgrade.go +++ b/internal/operator/cluster/upgrade.go @@ -497,6 +497,13 @@ func preparePgclusterForUpgrade(pgcluster *crv1.Pgcluster, parameters map[string delete(pgcluster.Spec.UserLabels, "service-type") } + // 4.6.0 moved the "autofail" label to the DisableAutofail attribute. Given + // by default we need to start in an autofailover state, we just delete the + // legacy attribute + if _, ok := pgcluster.ObjectMeta.GetLabels()["autofail"]; ok { + delete(pgcluster.ObjectMeta.Labels, "autofail") + } + // since the current primary label is not used in this version of the Postgres Operator, // delete it before moving on to other upgrade tasks delete(pgcluster.ObjectMeta.Labels, config.LABEL_CURRENT_PRIMARY) @@ -533,10 +540,10 @@ func preparePgclusterForUpgrade(pgcluster *crv1.Pgcluster, parameters map[string // use with PostGIS enabled pgclusters pgcluster.Spec.CCPImageTag = parameters[config.LABEL_CCP_IMAGE_KEY] - // set a default autofail value of "true" to enable Patroni's replication. If left to an existing - // value of "false," Patroni will be in a paused state and unable to sync all replicas to the - // current timeline - pgcluster.ObjectMeta.Labels[config.LABEL_AUTOFAIL] = "true" + // set a default disable autofail value of "false" to enable Patroni's replication. + // If left to an existing value of "true," Patroni will be in a paused state + // and unable to sync all replicas to the current timeline + pgcluster.Spec.DisableAutofail = false // Don't think we'll need to do this, but leaving the comment for now.... // pgcluster.ObjectMeta.Labels[config.LABEL_POD_ANTI_AFFINITY] = "" diff --git a/internal/util/cluster.go b/internal/util/cluster.go index 9bab736209..b92ea1c587 100644 --- a/internal/util/cluster.go +++ b/internal/util/cluster.go @@ -231,16 +231,6 @@ func CreateBackrestRepoSecrets(clientset kubernetes.Interface, return err } -// IsAutofailEnabled - returns true if autofail label is set to true, false if not. -func IsAutofailEnabled(cluster *crv1.Pgcluster) bool { - labels := cluster.ObjectMeta.Labels - failLabel := labels[config.LABEL_AUTOFAIL] - - log.Debugf("IsAutoFailEnabled: %s", failLabel) - - return failLabel == "true" -} - // GeneratedPasswordValidUntilDays returns the value for the number of days that // a password is valid for, which is used as part of PostgreSQL's VALID UNTIL // directive on a user. It first determines if the user provided this value via diff --git a/pkg/apis/crunchydata.com/v1/cluster.go b/pkg/apis/crunchydata.com/v1/cluster.go index 2347cfb637..e9a84e729c 100644 --- a/pkg/apis/crunchydata.com/v1/cluster.go +++ b/pkg/apis/crunchydata.com/v1/cluster.go @@ -49,6 +49,10 @@ type PgclusterSpec struct { CCPImagePrefix string `json:"ccpimageprefix"` PGOImagePrefix string `json:"pgoimageprefix"` Port string `json:"port"` + // DisableAutofail, if set to true, disables the autofail/HA capabilities + // We choose this, instead of the affirmative, so that way we default to + // autofail being on, given we're doing some legacy CRD stuff here + DisableAutofail bool `json:"disableAutofail"` // PGBadger, if set to true, enables the pgBadger sidecar PGBadger bool `json:"pgBadger"` PGBadgerPort string `json:"pgbadgerport"` From 9f790f52d34cfc45489799e81aed625d7cd41cc6 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Wed, 30 Dec 2020 16:24:53 -0500 Subject: [PATCH 096/276] Move pgBackRest storage type to CRD attribute This previously was driven by a label stored on a pgclusters.crunchydata.com custom resource. However, this is structured data, as well as data that drives cluster behavior, and as such it should be moved. This adds a new attribute called "backrestStorageTypes" which contains a list of acceptable storage types, i.e.: - posix - s3 "local" is kept for backwards compatibility purposes, but the upgrade attempts to force things to "posix". Additionally, if left empty, "posix" is assumed by the Postgres Operator. Modifying "backrestStorageTypes" on an existing cluster currently has no effect, though this is set up to be able to switch pgBackRest repositories during the lifetime of a cluster. These changes also have the effect of simplifying "pgo backup" behavior. If the command "pgo backup " is called, it will automagically issue backups to all available pgBackRest repositories. For example, a cluster that uses "s3" or "posix"/"s3" repositories will automatically have backups issued to them without specifying a target. The ability to specify taking a backup to a specific target still remains. The same applies to the restore in place functionality, as the default storage type is known. --- .../pgo-backrest-repo-sync.sh | 95 ---------- cmd/pgo/cmd/backup.go | 2 +- cmd/pgo/cmd/create.go | 6 +- .../content/architecture/disaster-recovery.md | 10 +- .../multi-cluster-kubernetes.md | 2 +- docs/content/architecture/overview.md | 2 +- docs/content/architecture/provisioning.md | 6 +- docs/content/custom-resources/_index.md | 15 +- docs/content/pgo-client/common-tasks.md | 2 +- .../pgo-client/reference/pgo_backup.md | 6 +- .../content/pgo-client/reference/pgo_clone.md | 45 ----- .../reference/pgo_create_cluster.md | 12 +- .../reference/pgo_create_schedule.md | 2 +- .../pgo-client/reference/pgo_restore.md | 4 +- docs/content/tutorial/disaster-recovery.md | 4 +- .../apiserver/backrestservice/backrestimpl.go | 96 +++++----- .../apiserver/clusterservice/clusterimpl.go | 60 ++++--- internal/apiserver/common.go | 64 ++++++- internal/apiserver/common_test.go | 143 +++++++++++++++ .../apiserver/scheduleservice/scheduleimpl.go | 4 +- internal/controller/job/backresthandler.go | 3 +- internal/controller/pod/inithandler.go | 3 +- internal/operator/backrest/backup.go | 34 +++- internal/operator/backrest/repo.go | 9 +- internal/operator/backrest/stanza.go | 27 +-- internal/operator/cluster/cluster.go | 2 +- internal/operator/cluster/clusterlogic.go | 110 ++++++------ internal/operator/cluster/upgrade.go | 44 +++++ internal/operator/clusterutilities.go | 24 ++- internal/operator/common.go | 59 +++++-- internal/operator/common_test.go | 165 ++++++++++++++++++ internal/util/backrest.go | 72 -------- pkg/apis/crunchydata.com/v1/cluster.go | 103 ++++++++--- pkg/apis/crunchydata.com/v1/cluster_test.go | 119 +++++++++++++ pkg/apis/crunchydata.com/v1/errors.go | 23 +++ pkg/apis/crunchydata.com/v1/task.go | 4 - 36 files changed, 911 insertions(+), 470 deletions(-) delete mode 100644 bin/pgo-backrest-repo-sync/pgo-backrest-repo-sync.sh delete mode 100644 docs/content/pgo-client/reference/pgo_clone.md create mode 100644 internal/operator/common_test.go create mode 100644 pkg/apis/crunchydata.com/v1/errors.go diff --git a/bin/pgo-backrest-repo-sync/pgo-backrest-repo-sync.sh b/bin/pgo-backrest-repo-sync/pgo-backrest-repo-sync.sh deleted file mode 100644 index 53e98e3a2e..0000000000 --- a/bin/pgo-backrest-repo-sync/pgo-backrest-repo-sync.sh +++ /dev/null @@ -1,95 +0,0 @@ -#!/bin/bash -x - -# Copyright 2019 - 2020 Crunchy Data Solutions, Inc. -# 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. - -function trap_sigterm() { - echo "Signal trap triggered, beginning shutdown.." - killall sshd -} - -trap 'trap_sigterm' SIGINT SIGTERM - -# First enable sshd prior to running rsync if using pgbackrest with a repository -# host -enable_sshd() { - SSHD_CONFIG=/sshd - - mkdir ~/.ssh/ - cp $SSHD_CONFIG/config ~/.ssh/ - cp $SSHD_CONFIG/id_ed25519 /tmp - chmod 400 /tmp/id_ed25519 ~/.ssh/config - - # start sshd which is used by pgbackrest for remote connections - /usr/sbin/sshd -D -f $SSHD_CONFIG/sshd_config & - - echo "sleep 5 secs to let sshd come up before running rsync command" - sleep 5 -} - -# Runs rync to sync from a specified source directory to a target directory -rsync_repo() { - echo "rsync pgbackrest from ${1} to ${2}" - # note, the "/" after the repo path is important, as we do not want to sync - # the top level directory - rsync -a --progress "${1}" "${2}" - echo "finished rsync" -} - -# Use the aws cli sync command to sync files from a source location to a target -# location. The this includes syncing files between who s3 locations, -# syncing a local directory to s3, or syncing from s3 to a local directory. -aws_sync_repo() { - export AWS_CA_BUNDLE="${PGBACKREST_REPO1_S3_CA_FILE}" - export AWS_ACCESS_KEY_ID="${PGBACKREST_REPO1_S3_KEY}" - export AWS_SECRET_ACCESS_KEY="${PGBACKREST_REPO1_S3_KEY_SECRET}" - export AWS_DEFAULT_REGION="${PGBACKREST_REPO1_S3_REGION}" - - echo "Executing aws s3 sync from source ${1} to target ${2}" - aws s3 sync "${1}" "${2}" - echo "Finished aws s3 sync" -} - -# If s3 is identifed as the data source, then the aws cli will be utilized to -# sync the repo to the target location in s3. If local storage is also enabled -# (along with s3) for the cluster, then also use the aws cli to sync the repo -# from s3 to the target volume locally. -# -# If the data source is local (the default if not specified at all), then first -# rsync the repo to the target directory locally. Then, if s3 storage is also -# enabled (along with local), use the aws cli to sync the local repo to the -# target s3 location. -if [[ "${BACKREST_STORAGE_SOURCE}" == "s3" ]] -then - aws_source="s3://${PGBACKREST_REPO1_S3_BUCKET}${PGBACKREST_REPO1_PATH}/" - aws_target="s3://${PGBACKREST_REPO1_S3_BUCKET}${NEW_PGBACKREST_REPO}/" - aws_sync_repo "${aws_source}" "${aws_target}" - if [[ "${PGHA_PGBACKREST_LOCAL_S3_STORAGE}" == "true" ]] - then - aws_source="s3://${PGBACKREST_REPO1_S3_BUCKET}${PGBACKREST_REPO1_PATH}/" - aws_target="${NEW_PGBACKREST_REPO}/" - aws_sync_repo "${aws_source}" "${aws_target}" - fi -else - enable_sshd # enable sshd for rsync - - rsync_source="${PGBACKREST_REPO1_HOST}:${PGBACKREST_REPO1_PATH}/" - rsync_target="$NEW_PGBACKREST_REPO" - rsync_repo "${rsync_source}" "${rsync_target}" - if [[ "${PGHA_PGBACKREST_LOCAL_S3_STORAGE}" == "true" ]] - then - aws_source="${NEW_PGBACKREST_REPO}/" - aws_target="s3://${PGBACKREST_REPO1_S3_BUCKET}${NEW_PGBACKREST_REPO}/" - aws_sync_repo "${aws_source}" "${aws_target}" - fi -fi diff --git a/cmd/pgo/cmd/backup.go b/cmd/pgo/cmd/backup.go index d70877a198..217c689f8e 100644 --- a/cmd/pgo/cmd/backup.go +++ b/cmd/pgo/cmd/backup.go @@ -91,7 +91,7 @@ func init() { backupCmd.Flags().StringVarP(&PVCName, "pvc-name", "", "", "The PVC name to use for the backup instead of the default.") backupCmd.Flags().StringVarP(&PGDumpDB, "database", "d", "postgres", "The name of the database pgdump will backup.") backupCmd.Flags().StringVar(&backupType, "backup-type", "pgbackrest", "The backup type to perform. Default is pgbackrest. Valid backup types are pgbackrest and pgdump.") - backupCmd.Flags().StringVarP(&BackrestStorageType, "pgbackrest-storage-type", "", "", "The type of storage to use when scheduling pgBackRest backups. Either \"local\", \"s3\" or both, comma separated. (default \"local\")") + backupCmd.Flags().StringVarP(&BackrestStorageType, "pgbackrest-storage-type", "", "", "The type of storage to use when scheduling pgBackRest backups. Either \"posix\", \"s3\" or both, comma separated. (default \"posix\")") } // deleteBackup .... diff --git a/cmd/pgo/cmd/create.go b/cmd/pgo/cmd/create.go index aff3f53dca..6b798e4a23 100644 --- a/cmd/pgo/cmd/create.go +++ b/cmd/pgo/cmd/create.go @@ -410,7 +410,7 @@ func init() { createClusterCmd.Flags().StringVar(&BackrestMemoryLimit, "pgbackrest-memory-limit", "", "Set the amount of memory to limit for "+ "the pgBackRest repository.") createClusterCmd.Flags().StringVarP(&BackrestPVCSize, "pgbackrest-pvc-size", "", "", - `The size of the PVC capacity for the pgBackRest repository. Overrides the value set in the storage class. This is ignored if the storage type of "local" is not used. Must follow the standard Kubernetes format, e.g. "10.1Gi"`) + `The size of the PVC capacity for the pgBackRest repository. Overrides the value set in the storage class. This is ignored if the storage type of "posix" is not used. Must follow the standard Kubernetes format, e.g. "10.1Gi"`) createClusterCmd.Flags().StringVarP(&BackrestRepoPath, "pgbackrest-repo-path", "", "", "The pgBackRest repository path that should be utilized instead of the default. Required "+ "for standby\nclusters to define the location of an existing pgBackRest repository.") @@ -435,7 +435,7 @@ func init() { createClusterCmd.Flags().StringVarP(&BackrestS3URIStyle, "pgbackrest-s3-uri-style", "", "", "Specifies whether \"host\" or \"path\" style URIs will be used when connecting to S3.") createClusterCmd.Flags().BoolVarP(&BackrestS3VerifyTLS, "pgbackrest-s3-verify-tls", "", true, "This sets if pgBackRest should verify the TLS certificate when connecting to S3. To disable, use \"--pgbackrest-s3-verify-tls=false\".") createClusterCmd.Flags().StringVar(&BackrestStorageConfig, "pgbackrest-storage-config", "", "The name of the storage config in pgo.yaml to use for the pgBackRest local repository.") - createClusterCmd.Flags().StringVarP(&BackrestStorageType, "pgbackrest-storage-type", "", "", "The type of storage to use with pgBackRest. Either \"local\", \"s3\" or both, comma separated. (default \"local\")") + createClusterCmd.Flags().StringVarP(&BackrestStorageType, "pgbackrest-storage-type", "", "", "The type of storage to use with pgBackRest. Either \"posix\", \"s3\" or both, comma separated. (default \"posix\")") createClusterCmd.Flags().BoolVarP(&BadgerFlag, "pgbadger", "", false, "Adds the crunchy-pgbadger container to the database pod.") createClusterCmd.Flags().BoolVarP(&PgbouncerFlag, "pgbouncer", "", false, "Adds a crunchy-pgbouncer deployment to the cluster.") createClusterCmd.Flags().StringVar(&PgBouncerCPURequest, "pgbouncer-cpu", "", "Set the number of millicores to request for CPU "+ @@ -540,7 +540,7 @@ func init() { // "pgo create schedule" flags createScheduleCmd.Flags().StringVarP(&ScheduleDatabase, "database", "", "", "The database to run the SQL policy against.") createScheduleCmd.Flags().StringVarP(&PGBackRestType, "pgbackrest-backup-type", "", "", "The type of pgBackRest backup to schedule (full, diff or incr).") - createScheduleCmd.Flags().StringVarP(&BackrestStorageType, "pgbackrest-storage-type", "", "", "The type of storage to use when scheduling pgBackRest backups. Either \"local\", \"s3\" or both, comma separated. (default \"local\")") + createScheduleCmd.Flags().StringVarP(&BackrestStorageType, "pgbackrest-storage-type", "", "", "The type of storage to use when scheduling pgBackRest backups. Either \"posix\", \"s3\" or both, comma separated. (default \"posix\")") createScheduleCmd.Flags().StringVarP(&CCPImageTag, "ccp-image-tag", "c", "", "The CCPImageTag to use for cluster creation. If specified, overrides the pgo.yaml setting.") createScheduleCmd.Flags().StringVarP(&SchedulePolicy, "policy", "", "", "The policy to use for SQL schedules.") createScheduleCmd.Flags().StringVarP(&Schedule, "schedule", "", "", "The schedule assigned to the cron task.") diff --git a/docs/content/architecture/disaster-recovery.md b/docs/content/architecture/disaster-recovery.md index 7d3639e0fc..e49a1be579 100644 --- a/docs/content/architecture/disaster-recovery.md +++ b/docs/content/architecture/disaster-recovery.md @@ -36,10 +36,10 @@ At PostgreSQL cluster creation time, you can specify a specific Storage Class for the pgBackRest repository. Additionally, you can also specify the type of pgBackRest repository that can be used, including: -- `local`: Uses the storage that is provided by the Kubernetes cluster's Storage +- `posix`: Uses the storage that is provided by the Kubernetes cluster's Storage Class that you select - `s3`: Use Amazon S3 or an object storage system that uses the S3 protocol -- `local,s3`: Use both the storage that is provided by the Kubernetes cluster's +- `posix,s3`: Use both the storage that is provided by the Kubernetes cluster's Storage Class that you select AND Amazon S3 (or equivalent object storage system that uses the S3 protocol) @@ -300,7 +300,7 @@ stored in Kubernetes Secrets and are securely mounted to the PostgreSQL clusters. To enable a PostgreSQL cluster to use S3, the `--pgbackrest-storage-type` on the -`pgo create cluster` command needs to be set to `s3` or `local,s3`. +`pgo create cluster` command needs to be set to `s3` or `posix,s3`. Once configured, the `pgo backup` and `pgo restore` commands will work with S3 similarly to the above! @@ -325,7 +325,7 @@ example pgBackRest repository in the state shown after running the ``` cluster: hippo -storage type: local +storage type: posix stanza: db status: ok @@ -377,7 +377,7 @@ Verify the backup is deleted with `pgo show backup hippo`: ``` cluster: hippo -storage type: local +storage type: posix stanza: db status: ok diff --git a/docs/content/architecture/high-availability/multi-cluster-kubernetes.md b/docs/content/architecture/high-availability/multi-cluster-kubernetes.md index f2be1e03c5..7944056673 100644 --- a/docs/content/architecture/high-availability/multi-cluster-kubernetes.md +++ b/docs/content/architecture/high-availability/multi-cluster-kubernetes.md @@ -142,7 +142,7 @@ pgBackRest. For example: ``` pgo create cluster hippo --pgbouncer --replica-count=2 \ - --pgbackrest-storage-type=local,s3 \ + --pgbackrest-storage-type=posix,s3 \ --pgbackrest-s3-key= \ --pgbackrest-s3-key-secret= \ --pgbackrest-s3-bucket=watering-hole \ diff --git a/docs/content/architecture/overview.md b/docs/content/architecture/overview.md index 9365787ba8..bf12101df1 100644 --- a/docs/content/architecture/overview.md +++ b/docs/content/architecture/overview.md @@ -78,7 +78,7 @@ built-in metrics and connection pooling, similar to: We can accomplish that with a single command: ```shell -pgo create cluster hacluster --replica-count=1 --metrics --pgbackrest-storage-type="local,s3" --pgbouncer --pgbadger +pgo create cluster hacluster --replica-count=1 --metrics --pgbackrest-storage-type="posix,s3" --pgbouncer --pgbadger ``` The PostgreSQL Operator handles setting up all of the various Deployments and diff --git a/docs/content/architecture/provisioning.md b/docs/content/architecture/provisioning.md index 23734ee180..a43d4baaba 100644 --- a/docs/content/architecture/provisioning.md +++ b/docs/content/architecture/provisioning.md @@ -49,8 +49,8 @@ allowing them to replay old WAL logs backups and perform full and point-in-time restores The pgBackRest repository can be configured to use storage that resides within -the Kubernetes cluster (the `local` option), Amazon S3 or a storage system that -uses the S3 protocol (the `s3` option), or both (`local,s3`). +the Kubernetes cluster (the `posix` option), Amazon S3 or a storage system that +uses the S3 protocol (the `s3` option), or both (`posix,s3`). Once the PostgreSQL primary instance is ready, there are two follow up actions that the PostgreSQL Operator takes to properly leverage the pgBackRest @@ -147,7 +147,7 @@ pgo create cluster mycluster2 --restore-from=mycluster1 ``` By default, pgBackRest will restore the latest backup available in the repository, and will replay -all available WAL archives. However, additional pgBackRest options can be specified using the +all available WAL archives. However, additional pgBackRest options can be specified using the `restore-opts` option, which allows the restore command to be further tailored and customized. For instance, the following demonstrates how a point-in-time restore can be utilized when creating a new cluster: diff --git a/docs/content/custom-resources/_index.md b/docs/content/custom-resources/_index.md index 8683722431..abc7706284 100644 --- a/docs/content/custom-resources/_index.md +++ b/docs/content/custom-resources/_index.md @@ -76,13 +76,10 @@ metadata: annotations: current-primary: ${pgo_cluster_name} labels: - autofail: "true" - crunchy-pgbadger: "false" crunchy-pgha-scope: ${pgo_cluster_name} deployment-name: ${pgo_cluster_name} name: ${pgo_cluster_name} pg-cluster: ${pgo_cluster_name} - pg-pod-anti-affinity: "" pgo-version: {{< param operatorVersion >}} pgouser: admin name: ${pgo_cluster_name} @@ -267,14 +264,10 @@ metadata: annotations: current-primary: ${pgo_cluster_name} labels: - autofail: "true" - backrest-storage-type: "s3" - crunchy-pgbadger: "false" crunchy-pgha-scope: ${pgo_cluster_name} deployment-name: ${pgo_cluster_name} name: ${pgo_cluster_name} pg-cluster: ${pgo_cluster_name} - pg-pod-anti-affinity: "" pgo-version: {{< param operatorVersion >}} pgouser: admin name: ${pgo_cluster_name} @@ -305,6 +298,8 @@ spec: storagetype: dynamic supplementalgroups: "" annotations: {} + backrestStorageTypes: + - s3 backrestS3Bucket: ${backrest_s3_bucket} backrestS3Endpoint: ${backrest_s3_endpoint} backrestS3Region: ${backrest_s3_region} @@ -332,7 +327,6 @@ spec: tolerations: [] user: hippo userlabels: - backrest-storage-type: "s3" pgo-version: {{< param operatorVersion >}} EOF @@ -397,13 +391,10 @@ metadata: annotations: current-primary: ${pgo_cluster_name} labels: - autofail: "true" - crunchy-pgbadger: "false" crunchy-pgha-scope: ${pgo_cluster_name} deployment-name: ${pgo_cluster_name} name: ${pgo_cluster_name} pg-cluster: ${pgo_cluster_name} - pg-pod-anti-affinity: "" pgo-version: {{< param operatorVersion >}} pgouser: admin name: ${pgo_cluster_name} @@ -554,7 +545,6 @@ spec: userlabels: NodeLabelKey: "" NodeLabelValue: "" - pg-pod-anti-affinity: "" pgo-version: {{< param operatorVersion >}} EOF @@ -740,6 +730,7 @@ make changes, as described below. | BackrestS3Bucket | `create` | An optional parameter that specifies a S3 bucket that pgBackRest should use. | | BackrestS3Endpoint | `create` | An optional parameter that specifies the S3 endpoint pgBackRest should use. | | BackrestS3Region | `create` | An optional parameter that specifies a cloud region that pgBackRest should use. | +| BackrestStorageTypes | `create` | An optional parameter that takes an array of different repositories types that can be used to store pgBackRest backups. Choices are `posix` and `s3`. If nothing is specified, it defaults to `posix`. (`local`, equivalent to `posix`, is available for backwards compatibility).| | BackrestS3URIStyle | `create` | An optional parameter that specifies if pgBackRest should use the `path` or `host` S3 URI style. | | BackrestS3VerifyTLS | `create` | An optional parameter that specifies if pgBackRest should verify the TLS endpoint. | | BackrestStorage | `create` | A specification that gives information about the storage attributes for the pgBackRest repository, which stores backups and archives, of the PostgreSQL cluster. For details, please see the `Storage Specification` section below. This is required. | diff --git a/docs/content/pgo-client/common-tasks.md b/docs/content/pgo-client/common-tasks.md index 02243ea62f..64ffd26a6a 100644 --- a/docs/content/pgo-client/common-tasks.md +++ b/docs/content/pgo-client/common-tasks.md @@ -1262,7 +1262,7 @@ specifications: ```shell pgo create cluster hippo --pgbouncer --replica-count=2 \ - --pgbackrest-storage-type=local,s3 \ + --pgbackrest-storage-type=posix,s3 \ --pgbackrest-s3-key= \ --pgbackrest-s3-key-secret= \ --pgbackrest-s3-bucket=watering-hole \ diff --git a/docs/content/pgo-client/reference/pgo_backup.md b/docs/content/pgo-client/reference/pgo_backup.md index 0e4c65a530..d8e028bf57 100644 --- a/docs/content/pgo-client/reference/pgo_backup.md +++ b/docs/content/pgo-client/reference/pgo_backup.md @@ -22,7 +22,7 @@ pgo backup [flags] --backup-type string The backup type to perform. Default is pgbackrest. Valid backup types are pgbackrest and pgdump. (default "pgbackrest") -d, --database string The name of the database pgdump will backup. (default "postgres") -h, --help help for backup - --pgbackrest-storage-type string The type of storage to use when scheduling pgBackRest backups. Either "local", "s3" or both, comma separated. (default "local") + --pgbackrest-storage-type string The type of storage to use when scheduling pgBackRest backups. Either "posix", "s3" or both, comma separated. (default "posix") --pvc-name string The PVC name to use for the backup instead of the default. -s, --selector string The selector to use for cluster filtering. ``` @@ -30,7 +30,7 @@ pgo backup [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -44,4 +44,4 @@ pgo backup [flags] * [pgo](/pgo-client/reference/pgo/) - The pgo command line interface. -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 30-Dec-2020 diff --git a/docs/content/pgo-client/reference/pgo_clone.md b/docs/content/pgo-client/reference/pgo_clone.md deleted file mode 100644 index 6f07741010..0000000000 --- a/docs/content/pgo-client/reference/pgo_clone.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -title: "pgo clone" ---- -## pgo clone - -Copies the primary database of an existing cluster to a new cluster - -### Synopsis - -Clone makes a copy of an existing PostgreSQL cluster managed by the Operator and creates a new PostgreSQL cluster managed by the Operator, with the data from the old cluster. - - pgo clone oldcluster newcluster - -``` -pgo clone [flags] -``` - -### Options - -``` - --enable-metrics If sets, enables metrics collection on the newly cloned cluster - -h, --help help for clone - --pgbackrest-pvc-size string The size of the PVC capacity for the pgBackRest repository. Overrides the value set in the storage class. This is ignored if the storage type of "local" is not used. Must follow the standard Kubernetes format, e.g. "10.1Gi" - --pgbackrest-storage-source string The data source for the clone when both "local" and "s3" are enabled in the source cluster. Either "local", "s3" or both, comma separated. (default "local") - --pvc-size string The size of the PVC capacity for primary and replica PostgreSQL instances. Overrides the value set in the storage class. Must follow the standard Kubernetes format, e.g. "10.1Gi" -``` - -### Options inherited from parent commands - -``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. - --debug Enable additional output for debugging. - --disable-tls Disable TLS authentication to the Postgres Operator. - --exclude-os-trust Exclude CA certs from OS default trust store - -n, --namespace string The namespace to use for pgo requests. - --pgo-ca-cert string The CA Certificate file path for authenticating to the PostgreSQL Operator apiserver. - --pgo-client-cert string The Client Certificate file path for authenticating to the PostgreSQL Operator apiserver. - --pgo-client-key string The Client Key file path for authenticating to the PostgreSQL Operator apiserver. -``` - -### SEE ALSO - -* [pgo](/pgo-client/reference/pgo/) - The pgo command line interface. - -###### Auto generated by spf13/cobra on 2-Jul-2020 diff --git a/docs/content/pgo-client/reference/pgo_create_cluster.md b/docs/content/pgo-client/reference/pgo_create_cluster.md index efc7edc738..36000d4253 100644 --- a/docs/content/pgo-client/reference/pgo_create_cluster.md +++ b/docs/content/pgo-client/reference/pgo_create_cluster.md @@ -21,7 +21,7 @@ pgo create cluster [flags] --annotation strings Add an Annotation to all of the managed deployments (PostgreSQL, pgBackRest, pgBouncer) The format to add an annotation is "name=value" The format to remove an annotation is "name-" - + For example, to add two annotations: "--annotation=hippo=awesome,elephant=cool" --annotation-pgbackrest strings Add an Annotation specifically to pgBackRest deployments The format to add an annotation is "name=value" @@ -59,7 +59,7 @@ pgo create cluster [flags] --pgbackrest-custom-config string The name of a ConfigMap containing pgBackRest configuration files. --pgbackrest-memory string Set the amount of memory to request for the pgBackRest repository. Defaults to server value (48Mi). --pgbackrest-memory-limit string Set the amount of memory to limit for the pgBackRest repository. - --pgbackrest-pvc-size string The size of the PVC capacity for the pgBackRest repository. Overrides the value set in the storage class. This is ignored if the storage type of "local" is not used. Must follow the standard Kubernetes format, e.g. "10.1Gi" + --pgbackrest-pvc-size string The size of the PVC capacity for the pgBackRest repository. Overrides the value set in the storage class. This is ignored if the storage type of "posix" is not used. Must follow the standard Kubernetes format, e.g. "10.1Gi" --pgbackrest-repo-path string The pgBackRest repository path that should be utilized instead of the default. Required for standby clusters to define the location of an existing pgBackRest repository. --pgbackrest-s3-bucket string The AWS S3 bucket that should be utilized for the cluster when the "s3" storage type is enabled for pgBackRest. @@ -71,7 +71,7 @@ pgo create cluster [flags] --pgbackrest-s3-uri-style string Specifies whether "host" or "path" style URIs will be used when connecting to S3. --pgbackrest-s3-verify-tls This sets if pgBackRest should verify the TLS certificate when connecting to S3. To disable, use "--pgbackrest-s3-verify-tls=false". (default true) --pgbackrest-storage-config string The name of the storage config in pgo.yaml to use for the pgBackRest local repository. - --pgbackrest-storage-type string The type of storage to use with pgBackRest. Either "local", "s3" or both, comma separated. (default "local") + --pgbackrest-storage-type string The type of storage to use with pgBackRest. Either "posix", "s3" or both, comma separated. (default "posix") --pgbadger Adds the crunchy-pgbadger container to the database pod. --pgbouncer Adds a crunchy-pgbouncer deployment to the cluster. --pgbouncer-cpu string Set the number of millicores to request for CPU for pgBouncer. Defaults to being unset. @@ -100,13 +100,13 @@ pgo create cluster [flags] --storage-config string The name of a Storage config in pgo.yaml to use for the cluster storage. --sync-replication Enables synchronous replication for the cluster. --tablespace strings Create a PostgreSQL tablespace on the cluster, e.g. "name=ts1:storageconfig=nfsstorage". The format is a key/value map that is delimited by "=" and separated by ":". The following parameters are available: - + - name (required): the name of the PostgreSQL tablespace - storageconfig (required): the storage configuration to use, as specified in the list available in the "pgo-config" ConfigMap (aka "pgo.yaml") - pvcsize: the size of the PVC capacity, which overrides the value set in the specified storageconfig. Follows the Kubernetes quantity format. - + For example, to create a tablespace with the NFS storage configuration with a PVC of size 10GiB: - + --tablespace=name=ts1:storageconfig=nfsstorage:pvcsize=10Gi --tls-only If true, forces all PostgreSQL connections to be over TLS. Must also set "server-tls-secret" and "server-ca-secret" --toleration strings Set Pod tolerations for each PostgreSQL instance in a cluster. diff --git a/docs/content/pgo-client/reference/pgo_create_schedule.md b/docs/content/pgo-client/reference/pgo_create_schedule.md index 4aeb07fe88..6549cfe588 100644 --- a/docs/content/pgo-client/reference/pgo_create_schedule.md +++ b/docs/content/pgo-client/reference/pgo_create_schedule.md @@ -22,7 +22,7 @@ pgo create schedule [flags] --database string The database to run the SQL policy against. -h, --help help for schedule --pgbackrest-backup-type string The type of pgBackRest backup to schedule (full, diff or incr). - --pgbackrest-storage-type string The type of storage to use when scheduling pgBackRest backups. Either "local", "s3" or both, comma separated. (default "local") + --pgbackrest-storage-type string The type of storage to use when scheduling pgBackRest backups. Either "posix", "s3" or both, comma separated. (default "posix") --policy string The policy to use for SQL schedules. --schedule string The schedule assigned to the cron task. --schedule-opts string The custom options passed to the create schedule API. diff --git a/docs/content/pgo-client/reference/pgo_restore.md b/docs/content/pgo-client/reference/pgo_restore.md index 2d8561c64d..e7e377c914 100644 --- a/docs/content/pgo-client/reference/pgo_restore.md +++ b/docs/content/pgo-client/reference/pgo_restore.md @@ -9,7 +9,7 @@ Perform a restore from previous backup RESTORE performs a restore to a new PostgreSQL cluster. This includes stopping the database and recreating a new primary with the restored data. Valid backup types to restore from are pgbackrest and pgdump. For example: - pgo restore mycluster + pgo restore mycluster ``` pgo restore [flags] @@ -24,7 +24,7 @@ pgo restore [flags] -h, --help help for restore --no-prompt No command line confirmation. --node-label string The node label (key=value) to use when scheduling the restore job, and in the case of a pgBackRest restore, also the new (i.e. restored) primary deployment. If not set, any node is used. - --pgbackrest-storage-type string The type of storage to use for a pgBackRest restore. Either "local", "s3". (default "local") + --pgbackrest-storage-type string The type of storage to use for a pgBackRest restore. Either "posix", "s3". (default "posix") -d, --pgdump-database string The name of the database pgdump will restore. (default "postgres") --pitr-target string The PITR target, being a PostgreSQL timestamp such as '2018-08-13 11:25:42.582117-04'. ``` diff --git a/docs/content/tutorial/disaster-recovery.md b/docs/content/tutorial/disaster-recovery.md index 0ad19d0fe3..2d87319ec9 100644 --- a/docs/content/tutorial/disaster-recovery.md +++ b/docs/content/tutorial/disaster-recovery.md @@ -200,7 +200,7 @@ Let's say that the `hippo` cluster currently has a set of backups that look like ``` cluster: hippo -storage type: local +storage type: posix stanza: db status: ok @@ -250,7 +250,7 @@ You can then verify the backup is deleted with `pgo show backup hippo`: ``` cluster: hippo -storage type: local +storage type: posix stanza: db status: ok diff --git a/internal/apiserver/backrestservice/backrestimpl.go b/internal/apiserver/backrestservice/backrestimpl.go index 1d545e387e..e743925d95 100644 --- a/internal/apiserver/backrestservice/backrestimpl.go +++ b/internal/apiserver/backrestservice/backrestimpl.go @@ -25,15 +25,15 @@ import ( "strings" "time" - "github.com/crunchydata/postgres-operator/internal/apiserver/backupoptions" - "github.com/crunchydata/postgres-operator/internal/operator" - "github.com/crunchydata/postgres-operator/internal/util" - "github.com/crunchydata/postgres-operator/internal/apiserver" + "github.com/crunchydata/postgres-operator/internal/apiserver/backupoptions" "github.com/crunchydata/postgres-operator/internal/config" "github.com/crunchydata/postgres-operator/internal/kubeapi" + "github.com/crunchydata/postgres-operator/internal/operator" + "github.com/crunchydata/postgres-operator/internal/util" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" + log "github.com/sirupsen/logrus" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -150,12 +150,14 @@ func CreateBackup(request *msgs.CreateBackrestBackupRequest, ns, pgouser string) return resp } - err = util.ValidateBackrestStorageTypeOnBackupRestore(request.BackrestStorageType, - cluster.Spec.UserLabels[config.LABEL_BACKREST_STORAGE_TYPE], false) - if err != nil { - resp.Status.Code = msgs.Error - resp.Status.Msg = err.Error() - return resp + // if a specific pgBackRest storage type was passed in to perform the + // backup, validate that this cluster can support it + if request.BackrestStorageType != "" { + if err := apiserver.ValidateBackrestStorageTypeForCommand(cluster, request.BackrestStorageType); err != nil { + resp.Status.Code = msgs.Error + resp.Status.Msg = err.Error() + return resp + } } err = apiserver.Clientset.CrunchydataV1().Pgtasks(ns).Delete(ctx, taskName, metav1.DeleteOptions{}) @@ -428,26 +430,17 @@ func ShowBackrest(name, selector, ns string) msgs.ShowBackrestResponse { // so we potentially add two "pieces of detail" based on whether or not we // have a local repository, a s3 repository, or both - storageTypes := c.Spec.UserLabels[config.LABEL_BACKREST_STORAGE_TYPE] - - for _, storageType := range apiserver.GetBackrestStorageTypes() { - - // so the way we currently store the different repos is not ideal, and - // this is not being fixed right now, so we'll follow this logic: - // - // 1. If storage type is "local" and the string either contains "local" or - // is empty, we can add the pgBackRest info - // 2. if the storage type is "s3" and the string contains "s3", we can - // add the pgBackRest info - // 3. Otherwise, continue - if (storageTypes == "" && storageType != "local") || (storageTypes != "" && !strings.Contains(storageTypes, storageType)) { - continue - } + storageTypes := c.Spec.BackrestStorageTypes + // if this happens to be empty, then the storage type is "posix" + if len(storageTypes) == 0 { + storageTypes = append(storageTypes, crv1.BackrestStorageTypePosix) + } + for _, storageType := range storageTypes { // begin preparing the detailed response detail := msgs.ShowBackrestDetail{ Name: c.Name, - StorageType: storageType, + StorageType: string(storageType), } verifyTLS, _ := strconv.ParseBool(operator.GetS3VerifyTLSSetting(c)) @@ -480,12 +473,12 @@ func ShowBackrest(name, selector, ns string) msgs.ShowBackrestResponse { return response } -func getInfo(storageType, podname, ns string, verifyTLS bool) (string, error) { +func getInfo(storageType crv1.BackrestStorageType, podname, ns string, verifyTLS bool) (string, error) { log.Debug("backrest info command requested") cmd := pgBackRestInfoCommand - if storageType == "s3" { + if storageType == crv1.BackrestStorageTypeS3 { cmd = append(cmd, repoTypeFlagS3...) if !verifyTLS { @@ -555,24 +548,21 @@ func Restore(request *msgs.RestoreRequest, ns, pgouser string) msgs.RestoreRespo return resp } - // ensure the backrest storage type specified for the backup is valid and enabled in the - // cluster - err = util.ValidateBackrestStorageTypeOnBackupRestore(request.BackrestStorageType, - cluster.Spec.UserLabels[config.LABEL_BACKREST_STORAGE_TYPE], true) - if err != nil { + // ensure the backrest storage type specified for the backup is valid and + // enabled in the cluster + if err := apiserver.ValidateBackrestStorageTypeForCommand(cluster, request.BackrestStorageType); err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() return resp } - var id string - id, err = createRestoreWorkflowTask(cluster.Name, ns) + id, err := createRestoreWorkflowTask(cluster) if err != nil { resp.Results = append(resp.Results, err.Error()) return resp } - pgtask, err := getRestoreParams(request, ns) + pgtask, err := getRestoreParams(cluster, request) if err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() @@ -607,18 +597,24 @@ func Restore(request *msgs.RestoreRequest, ns, pgouser string) msgs.RestoreRespo return resp } -func getRestoreParams(request *msgs.RestoreRequest, ns string) (*crv1.Pgtask, error) { +func getRestoreParams(cluster *crv1.Pgcluster, request *msgs.RestoreRequest) (*crv1.Pgtask, error) { var newInstance *crv1.Pgtask spec := crv1.PgtaskSpec{} - spec.Namespace = ns - spec.Name = "backrest-restore-" + request.FromCluster + spec.Namespace = cluster.Namespace + spec.Name = "backrest-restore-" + cluster.Name spec.TaskType = crv1.PgtaskBackrestRestore spec.Parameters = make(map[string]string) - spec.Parameters[config.LABEL_BACKREST_RESTORE_FROM_CLUSTER] = request.FromCluster + spec.Parameters[config.LABEL_BACKREST_RESTORE_FROM_CLUSTER] = cluster.Name spec.Parameters[config.LABEL_BACKREST_RESTORE_OPTS] = request.RestoreOpts spec.Parameters[config.LABEL_BACKREST_PITR_TARGET] = request.PITRTarget - spec.Parameters[config.LABEL_BACKREST_STORAGE_TYPE] = request.BackrestStorageType + + // get the repository to restore from. if not explicitly provided, the default + // for the cluster is used + spec.Parameters[config.LABEL_BACKREST_STORAGE_TYPE] = string(operator.GetRepoType(cluster)) + if request.BackrestStorageType != "" { + spec.Parameters[config.LABEL_BACKREST_STORAGE_TYPE] = request.BackrestStorageType + } // validate & parse nodeLabel if exists if request.NodeLabel != "" { @@ -635,7 +631,7 @@ func getRestoreParams(request *msgs.RestoreRequest, ns string) (*crv1.Pgtask, er newInstance = &crv1.Pgtask{ ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{config.LABEL_PG_CLUSTER: request.FromCluster}, + Labels: map[string]string{config.LABEL_PG_CLUSTER: cluster.Name}, Name: spec.Name, }, Spec: spec, @@ -643,26 +639,26 @@ func getRestoreParams(request *msgs.RestoreRequest, ns string) (*crv1.Pgtask, er return newInstance, nil } -func createRestoreWorkflowTask(clusterName, ns string) (string, error) { +func createRestoreWorkflowTask(cluster *crv1.Pgcluster) (string, error) { ctx := context.TODO() - taskName := clusterName + "-" + crv1.PgtaskWorkflowBackrestRestoreType + taskName := cluster.Name + "-" + crv1.PgtaskWorkflowBackrestRestoreType // delete any existing pgtask with the same name - if err := apiserver.Clientset.CrunchydataV1().Pgtasks(ns). + if err := apiserver.Clientset.CrunchydataV1().Pgtasks(cluster.Namespace). Delete(ctx, taskName, metav1.DeleteOptions{}); err != nil && !kubeapi.IsNotFound(err) { return "", err } // create pgtask CRD spec := crv1.PgtaskSpec{} - spec.Namespace = ns - spec.Name = clusterName + "-" + crv1.PgtaskWorkflowBackrestRestoreType + spec.Namespace = cluster.Namespace + spec.Name = cluster.Name + "-" + crv1.PgtaskWorkflowBackrestRestoreType spec.TaskType = crv1.PgtaskWorkflow spec.Parameters = make(map[string]string) spec.Parameters[crv1.PgtaskWorkflowSubmittedStatus] = time.Now().Format(time.RFC3339) - spec.Parameters[config.LABEL_PG_CLUSTER] = clusterName + spec.Parameters[config.LABEL_PG_CLUSTER] = cluster.Name u, err := ioutil.ReadFile("/proc/sys/kernel/random/uuid") if err != nil { @@ -678,10 +674,10 @@ func createRestoreWorkflowTask(clusterName, ns string) (string, error) { Spec: spec, } newInstance.ObjectMeta.Labels = make(map[string]string) - newInstance.ObjectMeta.Labels[config.LABEL_PG_CLUSTER] = clusterName + newInstance.ObjectMeta.Labels[config.LABEL_PG_CLUSTER] = cluster.Name newInstance.ObjectMeta.Labels[crv1.PgtaskWorkflowID] = spec.Parameters[crv1.PgtaskWorkflowID] - if _, err := apiserver.Clientset.CrunchydataV1().Pgtasks(ns). + if _, err := apiserver.Clientset.CrunchydataV1().Pgtasks(cluster.Namespace). Create(ctx, newInstance, metav1.CreateOptions{}); err != nil { log.Error(err) return "", err diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index 56f228bfa6..8307434c80 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -750,18 +750,13 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. // ensure the backrest storage type specified for the cluster is valid, and that the // configuration required to use that storage type (e.g. a bucket, endpoint and region // when using aws s3 storage) has been provided - err = validateBackrestStorageTypeOnCreate(request) + backrestStorageTypes, err := validateBackrestStorageTypeOnCreate(request) if err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() return resp } - if request.BackrestStorageType != "" { - log.Debug("using backrest storage type provided by user") - userLabelsMap[config.LABEL_BACKREST_STORAGE_TYPE] = request.BackrestStorageType - } - // if a value for BackrestStorageConfig is provided, validate it here if request.BackrestStorageConfig != "" && !apiserver.IsValidStorageName(request.BackrestStorageConfig) { resp.Status.Code = msgs.Error @@ -872,6 +867,7 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. // Create an instance of our CRD newInstance := getClusterParams(request, clusterName, userLabelsMap, ns) newInstance.ObjectMeta.Labels[config.LABEL_PGOUSER] = pgouser + newInstance.Spec.BackrestStorageTypes = backrestStorageTypes if request.SecretFrom != "" { err = validateSecretFrom(newInstance, request.SecretFrom) @@ -1905,12 +1901,18 @@ func UpdateCluster(request *msgs.UpdateClusterRequest) msgs.UpdateClusterRespons } // return an error if attempting to enable standby for a cluster that does not have the // required S3 settings - if cluster.Spec.Standby && - !strings.Contains(cluster.Spec.UserLabels[config.LABEL_BACKREST_STORAGE_TYPE], "s3") { - response.Status.Code = msgs.Error - response.Status.Msg = "Backrest storage type 's3' must be enabled in order to enable " + - "standby mode" - return response + if cluster.Spec.Standby { + s3Enabled := false + for _, storageType := range cluster.Spec.BackrestStorageTypes { + s3Enabled = s3Enabled || (storageType == crv1.BackrestStorageTypeS3) + } + + if !s3Enabled { + response.Status.Code = msgs.Error + response.Status.Msg = "Backrest storage type 's3' must be enabled in order to enable " + + "standby mode" + return response + } } // if a startup or shutdown was requested then update the pgcluster spec accordingly @@ -2132,19 +2134,33 @@ func setClusterAnnotationGroup(annotationGroup, annotations map[string]string) { // validateBackrestStorageTypeOnCreate validates the pgbackrest storage type specified when // a new cluster. This includes ensuring the type provided is valid, and that the required // configuration settings (s3 bucket, region, etc.) are also present -func validateBackrestStorageTypeOnCreate(request *msgs.CreateClusterRequest) error { - requestBackRestStorageType := request.BackrestStorageType +func validateBackrestStorageTypeOnCreate(request *msgs.CreateClusterRequest) ([]crv1.BackrestStorageType, error) { + storageTypes, err := crv1.ParseBackrestStorageTypes(request.BackrestStorageType) - if requestBackRestStorageType != "" && !util.IsValidBackrestStorageType(requestBackRestStorageType) { - return fmt.Errorf("Invalid value provided for pgBackRest storage type. The following values are allowed: %s", - "\""+strings.Join(apiserver.GetBackrestStorageTypes(), "\", \"")+"\"") - } else if strings.Contains(requestBackRestStorageType, "s3") && isMissingS3Config(request) { - return errors.New("A configuration setting for AWS S3 storage is missing. Values must be " + - "provided for the S3 bucket, S3 endpoint and S3 region in order to use the 's3' " + - "storage type with pgBackRest.") + if err != nil { + // if the error is due to no storage types elected, return an empty storage + // type slice. otherwise return an error + if errors.Is(err, crv1.ErrStorageTypesEmpty) { + return []crv1.BackrestStorageType{}, nil + } + + return nil, err } - return nil + // a special check -- if S3 storage is included, check to see if all of the + // appropriate settings are in place + for _, storageType := range storageTypes { + if storageType == crv1.BackrestStorageTypeS3 { + if isMissingS3Config(request) { + return nil, fmt.Errorf("A configuration setting for AWS S3 storage is missing. Values must be " + + "provided for the S3 bucket, S3 endpoint and S3 region in order to use the 's3' " + + "storage type with pgBackRest.") + } + break + } + } + + return storageTypes, nil } // validateClusterTLS validates the parameters that allow a user to enable TLS diff --git a/internal/apiserver/common.go b/internal/apiserver/common.go index a4c392a817..204f50fa70 100644 --- a/internal/apiserver/common.go +++ b/internal/apiserver/common.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" "strconv" + "strings" "github.com/crunchydata/postgres-operator/internal/config" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" @@ -41,7 +42,6 @@ const ( ) var ( - backrestStorageTypes = []string{"local", "s3"} // ErrDBContainerNotFound is an error that indicates that a "database" container // could not be found in a specific pod ErrDBContainerNotFound = errors.New("\"database\" container not found in pod") @@ -93,10 +93,6 @@ func CreateRMDataTask(clusterName, replicaName, taskName string, deleteBackups, return err } -func GetBackrestStorageTypes() []string { - return backrestStorageTypes -} - // IsValidPVC determines if a PVC with the name provided exits func IsValidPVC(pvcName, ns string) bool { ctx := context.TODO() @@ -112,6 +108,64 @@ func IsValidPVC(pvcName, ns string) bool { return pvc != nil } +// ValidateBackrestStorageTypeForCommand determines if a submitted pgBackRest +// storage value can be used as part of a pgBackRest operation based upon the +// storage types used by the PostgreSQL cluster itself +func ValidateBackrestStorageTypeForCommand(cluster *crv1.Pgcluster, storageTypeStr string) error { + // first, parse the submitted storage type string to see what we're up against + storageTypes, err := crv1.ParseBackrestStorageTypes(storageTypeStr) + + // if there is an error parsing the string and it's not due to the string + // being empty, return the error + // if it is due to an empty string, then return so that the defaults will be + // used + if err != nil { + if errors.Is(err, crv1.ErrStorageTypesEmpty) { + return nil + } + return err + } + + // there can only be one storage type used for a command (for now), so ensure + // this condition is sated + if len(storageTypes) > 1 { + return fmt.Errorf("you can only select one storage type") + } + + // a special case: the list of storage types is empty. if this is not a posix + // (or local) storage type, then return an error. Otherwise, we can exit here. + if len(cluster.Spec.BackrestStorageTypes) == 0 { + if !(storageTypes[0] == crv1.BackrestStorageTypePosix || storageTypes[0] == crv1.BackrestStorageTypeLocal) { + return fmt.Errorf("%w: choices are: posix", crv1.ErrInvalidStorageType) + } + return nil + } + + // now, see if the select storage type is available in the list of storage + // types on the cluster + ok := false + for _, storageType := range cluster.Spec.BackrestStorageTypes { + switch storageTypes[0] { + default: + ok = ok || (storageType == storageTypes[0]) + case crv1.BackrestStorageTypePosix, crv1.BackrestStorageTypeLocal: + ok = ok || (storageType == crv1.BackrestStorageTypePosix || storageType == crv1.BackrestStorageTypeLocal) + } + } + + if !ok { + choices := make([]string, len(cluster.Spec.BackrestStorageTypes)) + for i, storageType := range cluster.Spec.BackrestStorageTypes { + choices[i] = string(storageType) + } + + return fmt.Errorf("%w: choices are: %s", + crv1.ErrInvalidStorageType, strings.Join(choices, " ")) + } + + return nil +} + // ValidateResourceRequestLimit validates that a Kubernetes Requests/Limit pair // is valid, both by validating the values are valid quantity values, and then // by checking that the limit >= request. This also needs to check against the diff --git a/internal/apiserver/common_test.go b/internal/apiserver/common_test.go index da909d2ba6..2475fca015 100644 --- a/internal/apiserver/common_test.go +++ b/internal/apiserver/common_test.go @@ -18,9 +18,152 @@ limitations under the License. import ( "testing" + crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" + "k8s.io/apimachinery/pkg/api/resource" ) +func TestValidateBackrestStorageTypeForCommand(t *testing.T) { + cluster := &crv1.Pgcluster{ + Spec: crv1.PgclusterSpec{}, + } + + t.Run("empty repo type", func(t *testing.T) { + err := ValidateBackrestStorageTypeForCommand(cluster, "") + + if err != nil { + t.Fatalf("expected no error, actual error: %s", err.Error()) + } + }) + + t.Run("invalid repo type", func(t *testing.T) { + err := ValidateBackrestStorageTypeForCommand(cluster, "bad") + + if err == nil { + t.Fatalf("expected invalid repo type to return an error, no error returned") + } + }) + + t.Run("multiple repo types", func(t *testing.T) { + err := ValidateBackrestStorageTypeForCommand(cluster, "posix,s3") + + if err == nil { + t.Fatalf("expected error") + } + }) + + t.Run("posix repo, no repo types on resource", func(t *testing.T) { + cluster.Spec.BackrestStorageTypes = []crv1.BackrestStorageType{} + err := ValidateBackrestStorageTypeForCommand(cluster, "posix") + + if err != nil { + t.Fatalf("expected no error, actual error: %s", err.Error()) + } + }) + + t.Run("local repo, no repo types on resource", func(t *testing.T) { + cluster.Spec.BackrestStorageTypes = []crv1.BackrestStorageType{} + err := ValidateBackrestStorageTypeForCommand(cluster, "local") + + if err != nil { + t.Fatalf("expected no error, actual error: %s", err.Error()) + } + }) + + t.Run("s3 repo, no repo types on resource", func(t *testing.T) { + cluster.Spec.BackrestStorageTypes = []crv1.BackrestStorageType{} + err := ValidateBackrestStorageTypeForCommand(cluster, "s3") + + if err == nil { + t.Fatalf("expected error") + } + }) + + t.Run("posix repo, posix repo type available", func(t *testing.T) { + cluster.Spec.BackrestStorageTypes = []crv1.BackrestStorageType{crv1.BackrestStorageTypePosix} + err := ValidateBackrestStorageTypeForCommand(cluster, "posix") + + if err != nil { + t.Fatalf("expected no error, actual error: %s", err.Error()) + } + }) + + t.Run("posix repo, posix repo type unavailable", func(t *testing.T) { + cluster.Spec.BackrestStorageTypes = []crv1.BackrestStorageType{crv1.BackrestStorageTypeS3} + err := ValidateBackrestStorageTypeForCommand(cluster, "posix") + + if err == nil { + t.Fatalf("expected error") + } + }) + + t.Run("posix repo, local repo type available", func(t *testing.T) { + cluster.Spec.BackrestStorageTypes = []crv1.BackrestStorageType{crv1.BackrestStorageTypeLocal} + err := ValidateBackrestStorageTypeForCommand(cluster, "posix") + + if err != nil { + t.Fatalf("expected no error, actual error: %s", err.Error()) + } + }) + + t.Run("posix repo, multi-repo", func(t *testing.T) { + cluster.Spec.BackrestStorageTypes = []crv1.BackrestStorageType{ + crv1.BackrestStorageTypeS3, + crv1.BackrestStorageTypePosix, + } + err := ValidateBackrestStorageTypeForCommand(cluster, "posix") + + if err != nil { + t.Fatalf("expected no error, actual error: %s", err.Error()) + } + }) + + t.Run("local repo, local repo type available", func(t *testing.T) { + cluster.Spec.BackrestStorageTypes = []crv1.BackrestStorageType{crv1.BackrestStorageTypeLocal} + err := ValidateBackrestStorageTypeForCommand(cluster, "local") + + if err != nil { + t.Fatalf("expected no error, actual error: %s", err.Error()) + } + }) + + t.Run("local repo, local repo type unavailable", func(t *testing.T) { + cluster.Spec.BackrestStorageTypes = []crv1.BackrestStorageType{crv1.BackrestStorageTypeS3} + err := ValidateBackrestStorageTypeForCommand(cluster, "local") + + if err == nil { + t.Fatalf("expected error") + } + }) + + t.Run("local repo, posix repo type available", func(t *testing.T) { + cluster.Spec.BackrestStorageTypes = []crv1.BackrestStorageType{crv1.BackrestStorageTypePosix} + err := ValidateBackrestStorageTypeForCommand(cluster, "local") + + if err != nil { + t.Fatalf("expected no error, actual error: %s", err.Error()) + } + }) + + t.Run("s3 repo, s3 repo type available", func(t *testing.T) { + cluster.Spec.BackrestStorageTypes = []crv1.BackrestStorageType{crv1.BackrestStorageTypeS3} + err := ValidateBackrestStorageTypeForCommand(cluster, "s3") + + if err != nil { + t.Fatalf("expected no error, actual error: %s", err.Error()) + } + }) + + t.Run("s3 repo, s3 repo type unavailable", func(t *testing.T) { + cluster.Spec.BackrestStorageTypes = []crv1.BackrestStorageType{crv1.BackrestStorageTypePosix} + err := ValidateBackrestStorageTypeForCommand(cluster, "s3") + + if err == nil { + t.Fatalf("expected error") + } + }) +} + func TestValidateResourceRequestLimit(t *testing.T) { t.Run("valid", func(t *testing.T) { resources := []struct{ request, limit, defaultRequest string }{ diff --git a/internal/apiserver/scheduleservice/scheduleimpl.go b/internal/apiserver/scheduleservice/scheduleimpl.go index 8de3c624ea..86e3bdcfad 100644 --- a/internal/apiserver/scheduleservice/scheduleimpl.go +++ b/internal/apiserver/scheduleservice/scheduleimpl.go @@ -41,9 +41,7 @@ type scheduleRequest struct { func (s scheduleRequest) createBackRestSchedule(cluster *crv1.Pgcluster, ns string) *PgScheduleSpec { name := fmt.Sprintf("%s-%s-%s", cluster.Name, s.Request.ScheduleType, s.Request.PGBackRestType) - err := util.ValidateBackrestStorageTypeOnBackupRestore(s.Request.BackrestStorageType, - cluster.Spec.UserLabels[config.LABEL_BACKREST_STORAGE_TYPE], false) - if err != nil { + if err := apiserver.ValidateBackrestStorageTypeForCommand(cluster, s.Request.BackrestStorageType); err != nil { s.Response.Status.Code = msgs.Error s.Response.Status.Msg = err.Error() return &PgScheduleSpec{} diff --git a/internal/controller/job/backresthandler.go b/internal/controller/job/backresthandler.go index 40c10110ae..3bd90fcf83 100644 --- a/internal/controller/job/backresthandler.go +++ b/internal/controller/job/backresthandler.go @@ -102,7 +102,8 @@ func (c *Controller) handleBackrestBackupUpdate(job *apiv1.Job) error { return nil } -// handleBackrestRestoreUpdate is responsible for handling updates to backrest stanza create jobs +// handleBackrestStanzaCreateUpdate is responsible for handling updates to +// backrest stanza create jobs func (c *Controller) handleBackrestStanzaCreateUpdate(job *apiv1.Job) error { ctx := context.TODO() diff --git a/internal/controller/pod/inithandler.go b/internal/controller/pod/inithandler.go index 733d301cb7..7081c1dfbf 100644 --- a/internal/controller/pod/inithandler.go +++ b/internal/controller/pod/inithandler.go @@ -191,7 +191,8 @@ func (c *Controller) handleStandbyInit(cluster *crv1.Pgcluster) error { // a standby cluster that does not have "s3" storage only enabled. // If this is a standby cluster and the pgBackRest storage type is set // to "s3" for S3 storage only, set the cluster to an initialized status. - if cluster.Spec.UserLabels[config.LABEL_BACKREST_STORAGE_TYPE] != "s3" { + if !(len(cluster.Spec.BackrestStorageTypes) == 1 && + cluster.Spec.BackrestStorageTypes[0] == crv1.BackrestStorageTypeS3) { // first try to delete any existing stanza create task and/or job if err := c.Client.CrunchydataV1().Pgtasks(namespace). Delete(ctx, fmt.Sprintf("%s-%s", clusterName, crv1.PgtaskBackrestStanzaCreate), diff --git a/internal/operator/backrest/backup.go b/internal/operator/backrest/backup.go index e486104961..8cd150cdf3 100644 --- a/internal/operator/backrest/backup.go +++ b/internal/operator/backrest/backup.go @@ -56,7 +56,7 @@ type backrestJobTemplateFields struct { PgbackrestStanza string PgbackrestDBPath string PgbackrestRepo1Path string - PgbackrestRepo1Type string + PgbackrestRepo1Type crv1.BackrestStorageType BackrestLocalAndS3Storage bool PgbackrestS3VerifyTLS string PgbackrestRestoreVolumes string @@ -69,13 +69,36 @@ var ( ) // Backrest ... -func Backrest(namespace string, clientset kubernetes.Interface, task *crv1.Pgtask) { +func Backrest(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask) { ctx := context.TODO() - // create the Job to run the backrest command + // get the cluster that is requesting the backup. if we cannot get the cluster + // do not take the backup + cluster, err := clientset.CrunchydataV1().Pgclusters(task.Namespace).Get(ctx, + task.Spec.Parameters[config.LABEL_PG_CLUSTER], metav1.GetOptions{}) + + if err != nil { + log.Error(err) + return + } cmd := task.Spec.Parameters[config.LABEL_BACKREST_COMMAND] + // determine the repo type. we need to make a special check for a standby + // cluster (see below) + repoType := operator.GetRepoType(cluster) + + // If this is a standby cluster and the stanza creation task, if posix storage + // is specified then this ensures that the stanza is created on the local + // repository only. + // + //The stanza for the S3 repo will have already been created by the cluster + // the standby is replicating from, and therefore does not need to be + // attempted again. + if cluster.Spec.Standby && cmd == crv1.PgtaskBackrestStanzaCreate { + repoType = crv1.BackrestStorageTypePosix + } + // create the Job to run the backrest command jobFields := backrestJobTemplateFields{ JobName: task.Spec.Parameters[config.LABEL_JOB_NAME], ClusterName: task.Spec.Parameters[config.LABEL_PG_CLUSTER], @@ -91,8 +114,8 @@ func Backrest(namespace string, clientset kubernetes.Interface, task *crv1.Pgtas PgbackrestRepo1Path: task.Spec.Parameters[config.LABEL_PGBACKREST_REPO_PATH], PgbackrestRestoreVolumes: "", PgbackrestRestoreVolumeMounts: "", - PgbackrestRepo1Type: operator.GetRepoType(task.Spec.Parameters[config.LABEL_BACKREST_STORAGE_TYPE]), - BackrestLocalAndS3Storage: operator.IsLocalAndS3Storage(task.Spec.Parameters[config.LABEL_BACKREST_STORAGE_TYPE]), + PgbackrestRepo1Type: repoType, + BackrestLocalAndS3Storage: operator.IsLocalAndS3Storage(cluster), PgbackrestS3VerifyTLS: task.Spec.Parameters[config.LABEL_BACKREST_S3_VERIFY_TLS], } @@ -204,7 +227,6 @@ func CreateBackup(clientset pgo.Interface, namespace, clusterName, podName strin spec.Parameters[config.LABEL_IMAGE_PREFIX] = util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, operator.Pgo.Cluster.CCPImagePrefix) spec.Parameters[config.LABEL_BACKREST_COMMAND] = crv1.PgtaskBackrestBackup spec.Parameters[config.LABEL_BACKREST_OPTS] = backupOpts - spec.Parameters[config.LABEL_BACKREST_STORAGE_TYPE] = cluster.Spec.UserLabels[config.LABEL_BACKREST_STORAGE_TYPE] // Get 'true' or 'false' for setting the pgBackRest S3 verify TLS value spec.Parameters[config.LABEL_BACKREST_S3_VERIFY_TLS] = operator.GetS3VerifyTLSSetting(cluster) diff --git a/internal/operator/backrest/repo.go b/internal/operator/backrest/repo.go index f36fee6dce..28debffc17 100644 --- a/internal/operator/backrest/repo.go +++ b/internal/operator/backrest/repo.go @@ -55,7 +55,7 @@ type RepoDeploymentTemplateFields struct { PgbackrestPGPort string SshdPort int PgbackrestStanza string - PgbackrestRepo1Type string + PgbackrestRepo1Type crv1.BackrestStorageType PgbackrestS3EnvVars string Name string ClusterName string @@ -225,7 +225,6 @@ func setBootstrapRepoOverrides(clientset kubernetes.Interface, cluster *crv1.Pgc // specific PostgreSQL cluster. func getRepoDeploymentFields(clientset kubernetes.Interface, cluster *crv1.Pgcluster, replicas int) *RepoDeploymentTemplateFields { - namespace := cluster.GetNamespace() repoFields := RepoDeploymentTemplateFields{ CCPImagePrefix: util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, operator.Pgo.Cluster.CCPImagePrefix), @@ -234,13 +233,13 @@ func getRepoDeploymentFields(clientset kubernetes.Interface, cluster *crv1.Pgclu BackrestRepoClaimName: fmt.Sprintf(util.BackrestRepoPVCName, cluster.Name), SshdSecretsName: fmt.Sprintf(util.BackrestRepoSecretName, cluster.Name), PGbackrestDBHost: cluster.Name, - PgbackrestRepo1Path: util.GetPGBackRestRepoPath(*cluster), + PgbackrestRepo1Path: operator.GetPGBackRestRepoPath(cluster), PgbackrestDBPath: "/pgdata/" + cluster.Name, PgbackrestPGPort: cluster.Spec.Port, SshdPort: operator.Pgo.Cluster.BackrestPort, PgbackrestStanza: "db", - PgbackrestRepo1Type: operator.GetRepoType(cluster.Spec.UserLabels[config.LABEL_BACKREST_STORAGE_TYPE]), - PgbackrestS3EnvVars: operator.GetPgbackrestS3EnvVars(*cluster, clientset, namespace), + PgbackrestRepo1Type: operator.GetRepoType(cluster), + PgbackrestS3EnvVars: operator.GetPgbackrestS3EnvVars(clientset, *cluster), Name: fmt.Sprintf(util.BackrestRepoServiceName, cluster.Name), ClusterName: cluster.Name, SecurityContext: operator.GetPodSecurityContext(cluster.Spec.BackrestStorage.GetSupplementalGroups()), diff --git a/internal/operator/backrest/stanza.go b/internal/operator/backrest/stanza.go index a2a0176452..ba97c225c1 100644 --- a/internal/operator/backrest/stanza.go +++ b/internal/operator/backrest/stanza.go @@ -17,7 +17,6 @@ package backrest import ( "context" - "strings" "github.com/crunchydata/postgres-operator/internal/config" "github.com/crunchydata/postgres-operator/internal/kubeapi" @@ -94,31 +93,17 @@ func StanzaCreate(namespace, clusterName string, clientset kubeapi.Interface) { // this will be used by the associated backrest job spec.Parameters[config.LABEL_IMAGE_PREFIX] = util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, operator.Pgo.Cluster.CCPImagePrefix) spec.Parameters[config.LABEL_BACKREST_COMMAND] = crv1.PgtaskBackrestStanzaCreate + // Get 'true' or 'false' for setting the pgBackRest S3 verify TLS value + spec.Parameters[config.LABEL_BACKREST_S3_VERIFY_TLS] = operator.GetS3VerifyTLSSetting(cluster) - // Handle stanza creation for a standby cluster, which requires some additional consideration. - // This includes setting the pgBackRest storage type and command options as needed to support - // stanza creation for a standby cluster. If not a standby cluster then simply set the - // storage type and options as usual. + // Handle stanza creation for a standby cluster, which requires some + // additional consideration. + // Since the primary will not be directly accessible to the standby cluster, + // ensure the stanza created in offline mode if cluster.Spec.Standby { - // Since this is a standby cluster, if local storage is specified then ensure stanza - // creation is for the local repo only. The stanza for the S3 repo will have already been - // created by the cluster the standby is replicating from, and therefore does not need to - // be attempted again. - if strings.Contains(cluster.Spec.UserLabels[config.LABEL_BACKREST_STORAGE_TYPE], "local") { - spec.Parameters[config.LABEL_BACKREST_STORAGE_TYPE] = "local" - } - // Since the primary will not be directly accessible to the standby cluster, create the - // stanza in offline mode spec.Parameters[config.LABEL_BACKREST_OPTS] = "--no-online" - } else { - spec.Parameters[config.LABEL_BACKREST_STORAGE_TYPE] = - cluster.Spec.UserLabels[config.LABEL_BACKREST_STORAGE_TYPE] - spec.Parameters[config.LABEL_BACKREST_OPTS] = "" } - // Get 'true' or 'false' for setting the pgBackRest S3 verify TLS value - spec.Parameters[config.LABEL_BACKREST_S3_VERIFY_TLS] = operator.GetS3VerifyTLSSetting(cluster) - newInstance := &crv1.Pgtask{ ObjectMeta: metav1.ObjectMeta{ Name: taskName, diff --git a/internal/operator/cluster/cluster.go b/internal/operator/cluster/cluster.go index b16a89829b..216300cc0e 100644 --- a/internal/operator/cluster/cluster.go +++ b/internal/operator/cluster/cluster.go @@ -683,7 +683,7 @@ func annotateBackrestSecret(clientset kubernetes.Interface, cluster *crv1.Pgclus secretName := fmt.Sprintf(util.BackrestRepoSecretName, clusterName) patch, err := kubeapi.NewMergePatch().Add("metadata", "annotations")(map[string]string{ config.ANNOTATION_PG_PORT: cluster.Spec.Port, - config.ANNOTATION_REPO_PATH: util.GetPGBackRestRepoPath(*cluster), + config.ANNOTATION_REPO_PATH: operator.GetPGBackRestRepoPath(cluster), config.ANNOTATION_S3_BUCKET: cfg(cl.BackrestS3Bucket, op.BackrestS3Bucket), config.ANNOTATION_S3_ENDPOINT: cfg(cl.BackrestS3Endpoint, op.BackrestS3Endpoint), config.ANNOTATION_S3_REGION: cfg(cl.BackrestS3Region, op.BackrestS3Region), diff --git a/internal/operator/cluster/clusterlogic.go b/internal/operator/cluster/clusterlogic.go index 86ea813208..66c109296f 100644 --- a/internal/operator/cluster/clusterlogic.go +++ b/internal/operator/cluster/clusterlogic.go @@ -306,35 +306,34 @@ func getClusterDeploymentFields(clientset kubernetes.Interface, // create the primary deployment deploymentFields := operator.DeploymentTemplateFields{ - Name: cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY], - IsInit: true, - Replicas: "0", - ClusterName: cl.Spec.Name, - Port: cl.Spec.Port, - CCPImagePrefix: util.GetValueOrDefault(cl.Spec.CCPImagePrefix, operator.Pgo.Cluster.CCPImagePrefix), - CCPImage: cl.Spec.CCPImage, - CCPImageTag: cl.Spec.CCPImageTag, - PVCName: dataVolume.InlineVolumeSource(), - DeploymentLabels: operator.GetLabelsFromMap(cl.Spec.UserLabels), - PodAnnotations: operator.GetAnnotations(cl, crv1.ClusterAnnotationPostgres), - PodLabels: operator.GetLabelsFromMap(cl.Spec.UserLabels), - DataPathOverride: cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY], - Database: cl.Spec.Database, - SecurityContext: operator.GetPodSecurityContext(supplementalGroups), - RootSecretName: crv1.UserSecretName(cl, crv1.PGUserSuperuser), - PrimarySecretName: crv1.UserSecretName(cl, crv1.PGUserReplication), - UserSecretName: crv1.UserSecretName(cl, cl.Spec.User), - NodeSelector: operator.GetAffinity(cl.Spec.UserLabels["NodeLabelKey"], cl.Spec.UserLabels["NodeLabelValue"], "In"), - PodAntiAffinity: operator.GetPodAntiAffinity(cl, crv1.PodAntiAffinityDeploymentDefault, cl.Spec.PodAntiAffinity.Default), - ContainerResources: operator.GetResourcesJSON(cl.Spec.Resources, cl.Spec.Limits), - ConfVolume: operator.GetConfVolume(clientset, cl, namespace), - ExporterAddon: operator.GetExporterAddon(cl.Spec), - BadgerAddon: operator.GetBadgerAddon(cl, cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY]), - PgmonitorEnvVars: operator.GetPgmonitorEnvVars(cl), - ScopeLabel: config.LABEL_PGHA_SCOPE, - PgbackrestEnvVars: operator.GetPgbackrestEnvVars(cl, cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY], - cl.Spec.Port, cl.Spec.UserLabels[config.LABEL_BACKREST_STORAGE_TYPE]), - PgbackrestS3EnvVars: operator.GetPgbackrestS3EnvVars(*cl, clientset, namespace), + Name: cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY], + IsInit: true, + Replicas: "0", + ClusterName: cl.Spec.Name, + Port: cl.Spec.Port, + CCPImagePrefix: util.GetValueOrDefault(cl.Spec.CCPImagePrefix, operator.Pgo.Cluster.CCPImagePrefix), + CCPImage: cl.Spec.CCPImage, + CCPImageTag: cl.Spec.CCPImageTag, + PVCName: dataVolume.InlineVolumeSource(), + DeploymentLabels: operator.GetLabelsFromMap(cl.Spec.UserLabels), + PodAnnotations: operator.GetAnnotations(cl, crv1.ClusterAnnotationPostgres), + PodLabels: operator.GetLabelsFromMap(cl.Spec.UserLabels), + DataPathOverride: cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY], + Database: cl.Spec.Database, + SecurityContext: operator.GetPodSecurityContext(supplementalGroups), + RootSecretName: crv1.UserSecretName(cl, crv1.PGUserSuperuser), + PrimarySecretName: crv1.UserSecretName(cl, crv1.PGUserReplication), + UserSecretName: crv1.UserSecretName(cl, cl.Spec.User), + NodeSelector: operator.GetAffinity(cl.Spec.UserLabels["NodeLabelKey"], cl.Spec.UserLabels["NodeLabelValue"], "In"), + PodAntiAffinity: operator.GetPodAntiAffinity(cl, crv1.PodAntiAffinityDeploymentDefault, cl.Spec.PodAntiAffinity.Default), + ContainerResources: operator.GetResourcesJSON(cl.Spec.Resources, cl.Spec.Limits), + ConfVolume: operator.GetConfVolume(clientset, cl, namespace), + ExporterAddon: operator.GetExporterAddon(cl.Spec), + BadgerAddon: operator.GetBadgerAddon(cl, cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY]), + PgmonitorEnvVars: operator.GetPgmonitorEnvVars(cl), + ScopeLabel: config.LABEL_PGHA_SCOPE, + PgbackrestEnvVars: operator.GetPgbackrestEnvVars(cl, cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY], cl.Spec.Port), + PgbackrestS3EnvVars: operator.GetPgbackrestS3EnvVars(clientset, *cl), ReplicaReinitOnStartFail: !operator.Pgo.Cluster.DisableReplicaStartFailReinit, SyncReplication: operator.GetSyncReplication(cl.Spec.SyncReplication), Tablespaces: operator.GetTablespaceNames(cl.Spec.TablespaceMounts), @@ -463,33 +462,32 @@ func scaleReplicaCreateDeployment(clientset kubernetes.Interface, // create the replica deployment replicaDeploymentFields := operator.DeploymentTemplateFields{ - Name: replica.Spec.Name, - ClusterName: replica.Spec.ClusterName, - Port: cluster.Spec.Port, - CCPImagePrefix: util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, operator.Pgo.Cluster.CCPImagePrefix), - CCPImageTag: imageTag, - CCPImage: image, - PVCName: dataVolume.InlineVolumeSource(), - Database: cluster.Spec.Database, - DataPathOverride: replica.Spec.Name, - Replicas: "1", - ConfVolume: operator.GetConfVolume(clientset, cluster, namespace), - DeploymentLabels: operator.GetLabelsFromMap(cluster.Spec.UserLabels), - PodAnnotations: operator.GetAnnotations(cluster, crv1.ClusterAnnotationPostgres), - PodLabels: operator.GetLabelsFromMap(cluster.Spec.UserLabels), - SecurityContext: operator.GetPodSecurityContext(supplementalGroups), - RootSecretName: crv1.UserSecretName(cluster, crv1.PGUserSuperuser), - PrimarySecretName: crv1.UserSecretName(cluster, crv1.PGUserReplication), - UserSecretName: crv1.UserSecretName(cluster, cluster.Spec.User), - ContainerResources: operator.GetResourcesJSON(cluster.Spec.Resources, cluster.Spec.Limits), - NodeSelector: operator.GetAffinity(replica.Spec.UserLabels["NodeLabelKey"], replica.Spec.UserLabels["NodeLabelValue"], "In"), - PodAntiAffinity: operator.GetPodAntiAffinity(cluster, crv1.PodAntiAffinityDeploymentDefault, cluster.Spec.PodAntiAffinity.Default), - ExporterAddon: operator.GetExporterAddon(cluster.Spec), - BadgerAddon: operator.GetBadgerAddon(cluster, replica.Spec.Name), - ScopeLabel: config.LABEL_PGHA_SCOPE, - PgbackrestEnvVars: operator.GetPgbackrestEnvVars(cluster, replica.Spec.Name, - cluster.Spec.Port, cluster.Spec.UserLabels[config.LABEL_BACKREST_STORAGE_TYPE]), - PgbackrestS3EnvVars: operator.GetPgbackrestS3EnvVars(*cluster, clientset, namespace), + Name: replica.Spec.Name, + ClusterName: replica.Spec.ClusterName, + Port: cluster.Spec.Port, + CCPImagePrefix: util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, operator.Pgo.Cluster.CCPImagePrefix), + CCPImageTag: imageTag, + CCPImage: image, + PVCName: dataVolume.InlineVolumeSource(), + Database: cluster.Spec.Database, + DataPathOverride: replica.Spec.Name, + Replicas: "1", + ConfVolume: operator.GetConfVolume(clientset, cluster, namespace), + DeploymentLabels: operator.GetLabelsFromMap(cluster.Spec.UserLabels), + PodAnnotations: operator.GetAnnotations(cluster, crv1.ClusterAnnotationPostgres), + PodLabels: operator.GetLabelsFromMap(cluster.Spec.UserLabels), + SecurityContext: operator.GetPodSecurityContext(supplementalGroups), + RootSecretName: crv1.UserSecretName(cluster, crv1.PGUserSuperuser), + PrimarySecretName: crv1.UserSecretName(cluster, crv1.PGUserReplication), + UserSecretName: crv1.UserSecretName(cluster, cluster.Spec.User), + ContainerResources: operator.GetResourcesJSON(cluster.Spec.Resources, cluster.Spec.Limits), + NodeSelector: operator.GetAffinity(replica.Spec.UserLabels["NodeLabelKey"], replica.Spec.UserLabels["NodeLabelValue"], "In"), + PodAntiAffinity: operator.GetPodAntiAffinity(cluster, crv1.PodAntiAffinityDeploymentDefault, cluster.Spec.PodAntiAffinity.Default), + ExporterAddon: operator.GetExporterAddon(cluster.Spec), + BadgerAddon: operator.GetBadgerAddon(cluster, replica.Spec.Name), + ScopeLabel: config.LABEL_PGHA_SCOPE, + PgbackrestEnvVars: operator.GetPgbackrestEnvVars(cluster, replica.Spec.Name, cluster.Spec.Port), + PgbackrestS3EnvVars: operator.GetPgbackrestS3EnvVars(clientset, *cluster), ReplicaReinitOnStartFail: !operator.Pgo.Cluster.DisableReplicaStartFailReinit, SyncReplication: operator.GetSyncReplication(cluster.Spec.SyncReplication), Tablespaces: operator.GetTablespaceNames(cluster.Spec.TablespaceMounts), diff --git a/internal/operator/cluster/upgrade.go b/internal/operator/cluster/upgrade.go index 639f46ae41..bebdeea866 100644 --- a/internal/operator/cluster/upgrade.go +++ b/internal/operator/cluster/upgrade.go @@ -21,6 +21,7 @@ import ( "fmt" "io/ioutil" "strconv" + "strings" "time" "github.com/crunchydata/postgres-operator/internal/config" @@ -504,6 +505,49 @@ func preparePgclusterForUpgrade(pgcluster *crv1.Pgcluster, parameters map[string delete(pgcluster.ObjectMeta.Labels, "autofail") } + // 4.6.0 moved the "backrest-storage-type" label to a CRD attribute, well, + // really an array of CRD attributes, which we need to map the various + // attributes to. "local" will be mapped the "posix" to match the pgBackRest + // nomenclature + // + // If we come back with an empty array, we will default it to posix + if val, ok := pgcluster.Spec.UserLabels["backrest-storage-type"]; ok { + pgcluster.Spec.BackrestStorageTypes = make([]crv1.BackrestStorageType, 0) + storageTypes := strings.Split(val, ",") + + // loop through each of the storage types processed and determine which of + // the standard storage types it matches + for _, s := range storageTypes { + for _, storageType := range crv1.BackrestStorageTypes { + // if this is not the storage type, continue looping + if crv1.BackrestStorageType(s) != storageType { + continue + } + + // so this is the storage type. However, if it's "local" let's update + // it to be posix + if storageType == crv1.BackrestStorageTypeLocal { + pgcluster.Spec.BackrestStorageTypes = append(pgcluster.Spec.BackrestStorageTypes, + crv1.BackrestStorageTypePosix) + } else { + pgcluster.Spec.BackrestStorageTypes = append(pgcluster.Spec.BackrestStorageTypes, storageType) + } + + // we can break the inner loop + break + } + } + + // remember: if somehow this is empty, add "posix" + if len(pgcluster.Spec.BackrestStorageTypes) == 0 { + pgcluster.Spec.BackrestStorageTypes = append(pgcluster.Spec.BackrestStorageTypes, + crv1.BackrestStorageTypePosix) + } + + // and delete the label + delete(pgcluster.Spec.UserLabels, "backrest-storage-type") + } + // since the current primary label is not used in this version of the Postgres Operator, // delete it before moving on to other upgrade tasks delete(pgcluster.ObjectMeta.Labels, config.LABEL_CURRENT_PRIMARY) diff --git a/internal/operator/clusterutilities.go b/internal/operator/clusterutilities.go index 1095fbb2b2..6497d95262 100644 --- a/internal/operator/clusterutilities.go +++ b/internal/operator/clusterutilities.go @@ -114,7 +114,7 @@ type PgbackrestEnvVarsTemplateFields struct { PgbackrestDBPath string PgbackrestRepo1Path string PgbackrestRepo1Host string - PgbackrestRepo1Type string + PgbackrestRepo1Type crv1.BackrestStorageType PgbackrestLocalAndS3Storage bool PgbackrestPGPort string } @@ -276,15 +276,15 @@ func GetAnnotations(cluster *crv1.Pgcluster, annotationType crv1.ClusterAnnotati } // consolidate with cluster.GetPgbackrestEnvVars -func GetPgbackrestEnvVars(cluster *crv1.Pgcluster, depName, port, storageType string) string { +func GetPgbackrestEnvVars(cluster *crv1.Pgcluster, depName, port string) string { fields := PgbackrestEnvVarsTemplateFields{ PgbackrestStanza: "db", PgbackrestRepo1Host: cluster.Name + "-backrest-shared-repo", - PgbackrestRepo1Path: util.GetPGBackRestRepoPath(*cluster), + PgbackrestRepo1Path: GetPGBackRestRepoPath(cluster), PgbackrestDBPath: "/pgdata/" + depName, PgbackrestPGPort: port, - PgbackrestRepo1Type: GetRepoType(storageType), - PgbackrestLocalAndS3Storage: IsLocalAndS3Storage(storageType), + PgbackrestRepo1Type: GetRepoType(cluster), + PgbackrestLocalAndS3Storage: IsLocalAndS3Storage(cluster), } doc := bytes.Buffer{} @@ -306,7 +306,7 @@ func GetPgbackrestBootstrapEnvVars(restoreClusterName, depName string, PgbackrestRepo1Path: restoreFromSecret.Annotations[config.ANNOTATION_REPO_PATH], PgbackrestPGPort: restoreFromSecret.Annotations[config.ANNOTATION_PG_PORT], PgbackrestRepo1Host: fmt.Sprintf(util.BackrestRepoDeploymentName, restoreClusterName), - PgbackrestRepo1Type: "posix", // just set to the default, can be overridden via CLI args + PgbackrestRepo1Type: crv1.BackrestStorageTypePosix, // just set to the default, can be overridden via CLI args } var doc bytes.Buffer @@ -793,9 +793,15 @@ func GetPgmonitorEnvVars(cluster *crv1.Pgcluster) string { // pgBackRest environment variables required to enable S3 support. After the template has been // executed with the proper values, the result is then returned a string for inclusion in the PG // and pgBackRest deployments. -func GetPgbackrestS3EnvVars(cluster crv1.Pgcluster, clientset kubernetes.Interface, - ns string) string { - if !strings.Contains(cluster.Spec.UserLabels[config.LABEL_BACKREST_STORAGE_TYPE], "s3") { +func GetPgbackrestS3EnvVars(clientset kubernetes.Interface, cluster crv1.Pgcluster) string { + // determine if backups are enabled to be stored on S3 + isS3 := false + + for _, storageType := range cluster.Spec.BackrestStorageTypes { + isS3 = isS3 || (storageType == crv1.BackrestStorageTypeS3) + } + + if !isS3 { return "" } diff --git a/internal/operator/common.go b/internal/operator/common.go index 819fdc84d5..382fbb1498 100644 --- a/internal/operator/common.go +++ b/internal/operator/common.go @@ -19,10 +19,10 @@ import ( "bytes" "context" "encoding/json" + "fmt" "io/ioutil" "os" "path" - "strings" "github.com/crunchydata/postgres-operator/internal/config" "github.com/crunchydata/postgres-operator/internal/ns" @@ -36,6 +36,11 @@ import ( ) const ( + // defaultBackrestRepoPath defines the default repo1-path for pgBackRest for + // use when a specic path is not provided in the pgcluster CR. The '%s' + // format verb will be replaced with the cluster name when this variable is + // utilized + defaultBackrestRepoPath = "/backrestrepo/%s-backrest-shared-repo" // defaultBackrestRepoConfigPath contains the default configuration that are used // to set up a pgBackRest repository defaultBackrestRepoConfigPath = "/default-pgo-backrest-repo/" @@ -231,24 +236,56 @@ func GetResourcesJSON(resources, limits v1.ResourceList) string { return doc.String() } +// GetPGBackRestRepoPath is responsible for determining the repo path setting +// (i.e. 'repo1-path' flag) for use by pgBackRest. If a specific repo path has +// been defined in the pgcluster CR, then that path will be returned. Otherwise +// a default path will be returned that is generated from the cluster name +func GetPGBackRestRepoPath(cluster *crv1.Pgcluster) string { + if cluster.Spec.BackrestRepoPath != "" { + return cluster.Spec.BackrestRepoPath + } + return fmt.Sprintf(defaultBackrestRepoPath, cluster.Name) +} + // GetRepoType returns the proper repo type to set in container based on the // backrest storage type provided -func GetRepoType(backrestStorageType string) string { - if backrestStorageType != "" && backrestStorageType == "s3" { - return "s3" - } else { - return "posix" +// +// If there are multiple types, the default returned is "posix". This could +// change once there is proper multi-repo support, but with proper multi-repo +// support, this function is likely annhilated. +// +// If there is nothing, the default returned is posix +func GetRepoType(cluster *crv1.Pgcluster) crv1.BackrestStorageType { + // so...per the above comment... + if len(cluster.Spec.BackrestStorageTypes) == 0 || len(cluster.Spec.BackrestStorageTypes) > 1 { + return crv1.BackrestStorageTypePosix } + + // alright, so there is only 1. If it happens to be "local" ensure that posix + // is returned + if cluster.Spec.BackrestStorageTypes[0] == crv1.BackrestStorageTypeLocal { + return crv1.BackrestStorageTypePosix + } + + return cluster.Spec.BackrestStorageTypes[0] } // IsLocalAndS3Storage a boolean indicating whether or not local and s3 storage should // be enabled for pgBackRest based on the backrestStorageType string provided -func IsLocalAndS3Storage(backrestStorageType string) bool { - if backrestStorageType != "" && strings.Contains(backrestStorageType, "s3") && - strings.Contains(backrestStorageType, "local") { - return true +func IsLocalAndS3Storage(cluster *crv1.Pgcluster) bool { + // this works for the time being. if the counter is two or greater, then we + // have both local and S3 storage + i := 0 + + for _, storageType := range cluster.Spec.BackrestStorageTypes { + switch storageType { + default: // no -oop + case crv1.BackrestStorageTypeLocal, crv1.BackrestStorageTypePosix, crv1.BackrestStorageTypeS3: + i += 1 + } } - return false + + return i >= 2 } // SetContainerImageOverride determines if there is an override available for diff --git a/internal/operator/common_test.go b/internal/operator/common_test.go new file mode 100644 index 0000000000..53035c9933 --- /dev/null +++ b/internal/operator/common_test.go @@ -0,0 +1,165 @@ +package operator + +/* + Copyright 2020 Crunchy Data Solutions, Inc. + 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. +*/ + +import ( + "testing" + + crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" +) + +func TestGetRepoType(t *testing.T) { + cluster := &crv1.Pgcluster{ + Spec: crv1.PgclusterSpec{}, + } + + t.Run("empty list returns posix", func(t *testing.T) { + cluster.Spec.BackrestStorageTypes = make([]crv1.BackrestStorageType, 0) + + expected := crv1.BackrestStorageTypePosix + actual := GetRepoType(cluster) + if expected != actual { + t.Fatalf("expected %q, actual %q", expected, actual) + } + }) + + t.Run("multiple list returns posix", func(t *testing.T) { + cluster.Spec.BackrestStorageTypes = []crv1.BackrestStorageType{ + crv1.BackrestStorageTypeS3, + crv1.BackrestStorageTypePosix, + } + + expected := crv1.BackrestStorageTypePosix + actual := GetRepoType(cluster) + if expected != actual { + t.Fatalf("expected %q, actual %q", expected, actual) + } + }) + + t.Run("local returns posix", func(t *testing.T) { + cluster.Spec.BackrestStorageTypes = []crv1.BackrestStorageType{ + crv1.BackrestStorageTypeLocal, + } + + expected := crv1.BackrestStorageTypePosix + actual := GetRepoType(cluster) + if expected != actual { + t.Fatalf("expected %q, actual %q", expected, actual) + } + }) + + t.Run("posix returns posix", func(t *testing.T) { + cluster.Spec.BackrestStorageTypes = []crv1.BackrestStorageType{ + crv1.BackrestStorageTypePosix, + } + + expected := crv1.BackrestStorageTypePosix + actual := GetRepoType(cluster) + if expected != actual { + t.Fatalf("expected %q, actual %q", expected, actual) + } + }) + + t.Run("s3 returns s3", func(t *testing.T) { + cluster.Spec.BackrestStorageTypes = []crv1.BackrestStorageType{ + crv1.BackrestStorageTypeS3, + } + + expected := crv1.BackrestStorageTypeS3 + actual := GetRepoType(cluster) + if expected != actual { + t.Fatalf("expected %q, actual %q", expected, actual) + } + }) +} + +func TestIsLocalAndS3Storage(t *testing.T) { + cluster := &crv1.Pgcluster{ + Spec: crv1.PgclusterSpec{}, + } + + t.Run("empty list returns false", func(t *testing.T) { + cluster.Spec.BackrestStorageTypes = make([]crv1.BackrestStorageType, 0) + + expected := false + actual := IsLocalAndS3Storage(cluster) + if expected != actual { + t.Fatalf("expected %t, actual %t", expected, actual) + } + }) + + t.Run("posix only returns false", func(t *testing.T) { + cluster.Spec.BackrestStorageTypes = []crv1.BackrestStorageType{ + crv1.BackrestStorageTypePosix, + } + + expected := false + actual := IsLocalAndS3Storage(cluster) + if expected != actual { + t.Fatalf("expected %t, actual %t", expected, actual) + } + }) + + t.Run("local only returns false", func(t *testing.T) { + cluster.Spec.BackrestStorageTypes = []crv1.BackrestStorageType{ + crv1.BackrestStorageTypeLocal, + } + + expected := false + actual := IsLocalAndS3Storage(cluster) + if expected != actual { + t.Fatalf("expected %t, actual %t", expected, actual) + } + }) + + t.Run("s3 only returns false", func(t *testing.T) { + cluster.Spec.BackrestStorageTypes = []crv1.BackrestStorageType{ + crv1.BackrestStorageTypeS3, + } + + expected := false + actual := IsLocalAndS3Storage(cluster) + if expected != actual { + t.Fatalf("expected %t, actual %t", expected, actual) + } + }) + + t.Run("posix and s3 returns true", func(t *testing.T) { + cluster.Spec.BackrestStorageTypes = []crv1.BackrestStorageType{ + crv1.BackrestStorageTypePosix, + crv1.BackrestStorageTypeS3, + } + + expected := true + actual := IsLocalAndS3Storage(cluster) + if expected != actual { + t.Fatalf("expected %t, actual %t", expected, actual) + } + }) + + t.Run("local and s3 returns true", func(t *testing.T) { + cluster.Spec.BackrestStorageTypes = []crv1.BackrestStorageType{ + crv1.BackrestStorageTypeLocal, + crv1.BackrestStorageTypeS3, + } + + expected := true + actual := IsLocalAndS3Storage(cluster) + if expected != actual { + t.Fatalf("expected %t, actual %t", expected, actual) + } + }) +} diff --git a/internal/util/backrest.go b/internal/util/backrest.go index 2c42d2ae4b..a4b572f16b 100644 --- a/internal/util/backrest.go +++ b/internal/util/backrest.go @@ -15,14 +15,6 @@ package util limitations under the License. */ -import ( - "errors" - "fmt" - "strings" - - crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" -) - const ( BackrestRepoDeploymentName = "%s-backrest-shared-repo" BackrestRepoServiceName = "%s-backrest-shared-repo" @@ -30,67 +22,3 @@ const ( // #nosec: G101 BackrestRepoSecretName = "%s-backrest-repo-config" ) - -// defines the default repo1-path for pgBackRest for use when a specic path is not provided -// in the pgcluster CR. The '%s' format verb will be replaced with the cluster name when this -// variable is utilized -const defaultBackrestRepoPath = "/backrestrepo/%s-backrest-shared-repo" - -// ValidateBackrestStorageTypeOnBackupRestore checks to see if the pgbackrest storage type provided -// when performing either pgbackrest backup or restore is valid. This includes ensuring the value -// provided is a valid storage type (e.g. "s3" and/or "local"). This also includes ensuring the -// storage type specified (e.g. "s3" or "local") is enabled in the current cluster. And finally, -// validation is ocurring for a restore, the ensure only one storage type is selected. -func ValidateBackrestStorageTypeOnBackupRestore(newBackRestStorageType, - currentBackRestStorageType string, restore bool) error { - if newBackRestStorageType != "" && !IsValidBackrestStorageType(newBackRestStorageType) { - return fmt.Errorf("Invalid value provided for pgBackRest storage type. The following "+ - "values are allowed: %s", "\""+strings.Join(crv1.BackrestStorageTypes, "\", \"")+"\"") - } else if newBackRestStorageType != "" && - strings.Contains(newBackRestStorageType, "s3") && - !strings.Contains(currentBackRestStorageType, "s3") { - return errors.New("Storage type 's3' not allowed. S3 storage is not enabled for " + - "pgBackRest in this cluster") - } else if (newBackRestStorageType == "" || - strings.Contains(newBackRestStorageType, "local")) && - (currentBackRestStorageType != "" && - !strings.Contains(currentBackRestStorageType, "local")) { - return errors.New("Storage type 'local' not allowed. Local storage is not enabled for " + - "pgBackRest in this cluster. If this cluster uses S3 storage only, specify 's3' " + - "for the pgBackRest storage type.") - } - - // storage type validation that is only applicable for restores - if restore && newBackRestStorageType != "" && - len(strings.Split(newBackRestStorageType, ",")) > 1 { - return fmt.Errorf("Multiple storage types cannot be selected cannot be select when "+ - "performing a restore. Please select one of the following: %s", - "\""+strings.Join(crv1.BackrestStorageTypes, "\", \"")+"\"") - } - - return nil -} - -// IsValidBackrestStorageType determines if the storage source string contains valid pgBackRest -// storage type values -func IsValidBackrestStorageType(storageType string) bool { - isValid := true - for _, storageType := range strings.Split(storageType, ",") { - if !IsStringOneOf(storageType, crv1.BackrestStorageTypes...) { - isValid = false - break - } - } - return isValid -} - -// GetPGBackRestRepoPath is responsible for determining the repo path setting (i.e. 'repo1-path' -// flag) for use by pgBackRest. If a specific repo path has been defined in the pgcluster CR, -// then that path will be returned. Otherwise a default path will be returned, which is generated -// using the 'defaultBackrestRepoPath' constant and the cluster name. -func GetPGBackRestRepoPath(cluster crv1.Pgcluster) string { - if cluster.Spec.BackrestRepoPath != "" { - return cluster.Spec.BackrestRepoPath - } - return fmt.Sprintf(defaultBackrestRepoPath, cluster.Name) -} diff --git a/pkg/apis/crunchydata.com/v1/cluster.go b/pkg/apis/crunchydata.com/v1/cluster.go index e9a84e729c..76f7ccae67 100644 --- a/pkg/apis/crunchydata.com/v1/cluster.go +++ b/pkg/apis/crunchydata.com/v1/cluster.go @@ -17,6 +17,7 @@ package v1 import ( "fmt" + "strings" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -109,28 +110,33 @@ type PgclusterSpec struct { // PgBouncer contains all of the settings to properly maintain a pgBouncer // implementation - PgBouncer PgBouncerSpec `json:"pgBouncer"` - User string `json:"user"` - Database string `json:"database"` - Replicas string `json:"replicas"` - Status string `json:"status"` - CustomConfig string `json:"customconfig"` - UserLabels map[string]string `json:"userlabels"` - PodAntiAffinity PodAntiAffinitySpec `json:"podAntiAffinity"` - SyncReplication *bool `json:"syncReplication"` - BackrestConfig []v1.VolumeProjection `json:"backrestConfig"` - BackrestS3Bucket string `json:"backrestS3Bucket"` - BackrestS3Region string `json:"backrestS3Region"` - BackrestS3Endpoint string `json:"backrestS3Endpoint"` - BackrestS3URIStyle string `json:"backrestS3URIStyle"` - BackrestS3VerifyTLS string `json:"backrestS3VerifyTLS"` - BackrestRepoPath string `json:"backrestRepoPath"` - TablespaceMounts map[string]PgStorageSpec `json:"tablespaceMounts"` - TLS TLSSpec `json:"tls"` - TLSOnly bool `json:"tlsOnly"` - Standby bool `json:"standby"` - Shutdown bool `json:"shutdown"` - PGDataSource PGDataSourceSpec `json:"pgDataSource"` + PgBouncer PgBouncerSpec `json:"pgBouncer"` + User string `json:"user"` + Database string `json:"database"` + Replicas string `json:"replicas"` + Status string `json:"status"` + CustomConfig string `json:"customconfig"` + UserLabels map[string]string `json:"userlabels"` + PodAntiAffinity PodAntiAffinitySpec `json:"podAntiAffinity"` + SyncReplication *bool `json:"syncReplication"` + BackrestConfig []v1.VolumeProjection `json:"backrestConfig"` + BackrestS3Bucket string `json:"backrestS3Bucket"` + BackrestS3Region string `json:"backrestS3Region"` + BackrestS3Endpoint string `json:"backrestS3Endpoint"` + BackrestS3URIStyle string `json:"backrestS3URIStyle"` + BackrestS3VerifyTLS string `json:"backrestS3VerifyTLS"` + BackrestRepoPath string `json:"backrestRepoPath"` + // BackrestStorageTypes is a list of the different pgBackRest storage types + // to be used for this cluster. Presently, it can only accept up to local + // and S3, but is available to support different repo types in the future + // if the array is empty, "local" ("posix") is presumed. + BackrestStorageTypes []BackrestStorageType `json:"backrestStorageTypes"` + TablespaceMounts map[string]PgStorageSpec `json:"tablespaceMounts"` + TLS TLSSpec `json:"tls"` + TLSOnly bool `json:"tlsOnly"` + Standby bool `json:"standby"` + Shutdown bool `json:"shutdown"` + PGDataSource PGDataSourceSpec `json:"pgDataSource"` // Annotations contains a set of Deployment (and by association, Pod) // annotations that are propagated to all managed Deployments @@ -145,6 +151,28 @@ type PgclusterSpec struct { Tolerations []v1.Toleration `json:"tolerations"` } +// BackrestStorageType refers to the types of storage accept by pgBackRest +type BackrestStorageType string + +const ( + // BackrestStorageTypeLocal is DEPRECATED. It is the equivalent to "posix" + // storage and is the default storage available (well posix is the default). + // Available for legacy purposes -- this really maps to "posix" + BackrestStorageTypeLocal BackrestStorageType = "local" + // BackrestStorageTypePosix is the "posix" storage type and in the fullness + // of time should supercede local + BackrestStorageTypePosix BackrestStorageType = "posix" + // BackrestStorageTypeS3 if the S3 storage type for using S3 or S3-equivalent + // storage + BackrestStorageTypeS3 BackrestStorageType = "s3" +) + +var BackrestStorageTypes = []BackrestStorageType{ + BackrestStorageTypeLocal, + BackrestStorageTypePosix, + BackrestStorageTypeS3, +} + // ClusterAnnotations provides a set of annotations that can be propagated to // the managed deployments. These are subdivided into four categories, which // are explained further below: @@ -356,6 +384,37 @@ func (p PodAntiAffinityType) Validate() error { PodAntiAffinityRequired, PodAntiAffinityPreffered, PodAntiAffinityDisabled) } +// ParseBackrestStorageTypes takes a comma-delimited string of potential +// pgBackRest storage types and attempts to parse it into a recognizable array. +// if an invalid type is passed in, then an error is returned +func ParseBackrestStorageTypes(storageTypeStr string) ([]BackrestStorageType, error) { + storageTypes := make([]BackrestStorageType, 0) + + parsed := strings.Split(storageTypeStr, ",") + + // if no storage types found in the string, return + if len(parsed) == 1 && parsed[0] == "" { + return nil, ErrStorageTypesEmpty + } + + // iterate through the list and determine if there are valid storage types + // map all "local" into "posix" + for _, s := range parsed { + storageType := BackrestStorageType(s) + + switch storageType { + default: + return nil, fmt.Errorf("%w: %s", ErrInvalidStorageType, storageType) + case BackrestStorageTypePosix, BackrestStorageTypeLocal: + storageTypes = append(storageTypes, BackrestStorageTypePosix) + case BackrestStorageTypeS3: + storageTypes = append(storageTypes, storageType) + } + } + + return storageTypes, nil +} + // UserSecretName returns the name of a Kubernetes Secret representing the user. // Delegates to UserSecretNameFromClusterName. This is the preferred method // given there is less thinking for the caller to do, but there are some (one?) diff --git a/pkg/apis/crunchydata.com/v1/cluster_test.go b/pkg/apis/crunchydata.com/v1/cluster_test.go index c2f6c70bb3..4af663cf3e 100644 --- a/pkg/apis/crunchydata.com/v1/cluster_test.go +++ b/pkg/apis/crunchydata.com/v1/cluster_test.go @@ -16,12 +16,131 @@ package v1 */ import ( + "errors" "fmt" "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +func TestParseBackrestStorageTypes(t *testing.T) { + t.Run("empty", func(t *testing.T) { + _, err := ParseBackrestStorageTypes("") + + if !errors.Is(err, ErrStorageTypesEmpty) { + t.Fatalf("expected ErrStorageTypesEmpty actual %q", err.Error()) + } + }) + + t.Run("invalid", func(t *testing.T) { + _, err := ParseBackrestStorageTypes("bad bad bad") + + if !errors.Is(err, ErrInvalidStorageType) { + t.Fatalf("expected ErrInvalidStorageType actual %q", err.Error()) + } + + _, err = ParseBackrestStorageTypes("posix,bad") + + if !errors.Is(err, ErrInvalidStorageType) { + t.Fatalf("expected ErrInvalidStorageType actual %q", err.Error()) + } + }) + + t.Run("local should be posix", func(t *testing.T) { + storageTypes, err := ParseBackrestStorageTypes("local") + + if err != nil { + t.Fatalf("expected no error actual %q", err.Error()) + } + + if len(storageTypes) != 1 { + t.Fatalf("multiple storage types returned, expected 1") + } + + if storageTypes[0] != BackrestStorageTypePosix { + t.Fatalf("posix expected but not found") + } + }) + + t.Run("posix", func(t *testing.T) { + storageTypes, err := ParseBackrestStorageTypes("posix") + + if err != nil { + t.Fatalf("expected no error actual %q", err.Error()) + } + + if len(storageTypes) != 1 { + t.Fatalf("multiple storage types returned, expected 1") + } + + if storageTypes[0] != BackrestStorageTypePosix { + t.Fatalf("posix expected but not found") + } + }) + + t.Run("s3", func(t *testing.T) { + storageTypes, err := ParseBackrestStorageTypes("s3") + + if err != nil { + t.Fatalf("expected no error actual %q", err.Error()) + } + + if len(storageTypes) != 1 { + t.Fatalf("multiple storage types returned, expected 1") + } + + if storageTypes[0] != BackrestStorageTypeS3 { + t.Fatalf("s3 expected but not found") + } + }) + + t.Run("posix and s3", func(t *testing.T) { + storageTypes, err := ParseBackrestStorageTypes("posix,s3") + + if err != nil { + t.Fatalf("expected no error actual %q", err.Error()) + } + + if len(storageTypes) != 2 { + t.Fatalf("expected 2 storage types, actual %d", len(storageTypes)) + } + + posix := false + s3 := false + for _, storageType := range storageTypes { + posix = posix || (storageType == BackrestStorageTypePosix) + s3 = s3 || (storageType == BackrestStorageTypeS3) + } + + if !(posix && s3) { + t.Fatalf("posix and s3 expected but not found") + } + }) + + t.Run("local and s3", func(t *testing.T) { + storageTypes, err := ParseBackrestStorageTypes("local,s3") + + if err != nil { + t.Fatalf("expected no error actual %q", err.Error()) + } + + if len(storageTypes) != 2 { + t.Fatalf("expected 2 storage types, actual %d", len(storageTypes)) + } + + posix := false + s3 := false + for _, storageType := range storageTypes { + posix = posix || (storageType == BackrestStorageTypePosix) + s3 = s3 || (storageType == BackrestStorageTypeS3) + } + + if !(posix && s3) { + t.Fatalf("posix and s3 expected but not found") + } + }) +} + func TestUserSecretName(t *testing.T) { cluster := &Pgcluster{ ObjectMeta: metav1.ObjectMeta{ diff --git a/pkg/apis/crunchydata.com/v1/errors.go b/pkg/apis/crunchydata.com/v1/errors.go new file mode 100644 index 0000000000..6c8fddbb2d --- /dev/null +++ b/pkg/apis/crunchydata.com/v1/errors.go @@ -0,0 +1,23 @@ +package v1 + +import "errors" + +/* + Copyright 2020 Crunchy Data Solutions, Inc. + 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. +*/ + +var ( + ErrStorageTypesEmpty = errors.New("no storage types detected") + ErrInvalidStorageType = errors.New("invalid storage type") +) diff --git a/pkg/apis/crunchydata.com/v1/task.go b/pkg/apis/crunchydata.com/v1/task.go index 463c532f80..d6791c8415 100644 --- a/pkg/apis/crunchydata.com/v1/task.go +++ b/pkg/apis/crunchydata.com/v1/task.go @@ -84,10 +84,6 @@ const ( BackupTypeBootstrap string = "bootstrap" ) -// BackrestStorageTypes defines the valid types of storage that can be utilized -// with pgBackRest -var BackrestStorageTypes = []string{"local", "s3"} - // PgtaskSpec ... // swagger:ignore type PgtaskSpec struct { From 34e2da1b65841c54292e73d93900e16d80f2fd42 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Wed, 30 Dec 2020 21:47:25 -0500 Subject: [PATCH 097/276] Ensure pod anti-affinity is loaded from spec The pod anti-affinity had been driven from the pgcluster custom resource since its inception, but there were some vestiges in the form of labels on the custom resource. This moves all of the labelign to be driven by the custom resource attributes, which primarily affects the Postgres instances. This also includes some random documentation updates. --- docs/content/pgo-client/common-tasks.md | 2 +- examples/create-by-resource/fromcrd.json | 3 - .../create-cluster/templates/pgcluster.yaml | 1 - examples/kustomize/createcluster/README.md | 8 +- .../createcluster/base/pgcluster.yaml | 1 - .../overlay/staging/hippo-rpl1-pgreplica.yaml | 2 - .../files/pgo-configs/cluster-deployment.json | 1 + .../apiserver/clusterservice/clusterimpl.go | 3 - internal/operator/cluster/clusterlogic.go | 158 +++++++++--------- internal/operator/cluster/upgrade.go | 3 - internal/operator/clusterutilities.go | 48 +++--- 11 files changed, 113 insertions(+), 117 deletions(-) diff --git a/docs/content/pgo-client/common-tasks.md b/docs/content/pgo-client/common-tasks.md index 64ffd26a6a..d5ae6b8b33 100644 --- a/docs/content/pgo-client/common-tasks.md +++ b/docs/content/pgo-client/common-tasks.md @@ -384,7 +384,7 @@ cluster : hacluster (crunchy-postgres-ha:{{< param centosBase >}}-{{< param post deployment : hacluster deployment : hacluster-backrest-shared-repo service : hacluster - ClusterIP (10.102.20.42) - labels : pg-pod-anti-affinity= archive-timeout=60 crunchy-pgbadger=false crunchy-postgres-exporter=false deployment-name=hacluster pg-cluster=hacluster crunchy-pgha-scope=hacluster autofail=true pgo-backrest=true pgo-version={{< param operatorVersion >}} current-primary=hacluster name=hacluster pgouser=admin workflowid=ae714d12-f5d0-4fa9-910f-21944b41dec8 + labels : archive-timeout=60 deployment-name=hacluster pg-cluster=hacluster crunchy-pgha-scope=hacluster pgo-version={{< param operatorVersion >}} current-primary=hacluster name=hacluster pgouser=admin workflowid=ae714d12-f5d0-4fa9-910f-21944b41dec8 ``` ### Deleting a Cluster diff --git a/examples/create-by-resource/fromcrd.json b/examples/create-by-resource/fromcrd.json index 49e4665fe7..c267e258d5 100644 --- a/examples/create-by-resource/fromcrd.json +++ b/examples/create-by-resource/fromcrd.json @@ -6,13 +6,10 @@ "current-primary": "fromcrd" }, "labels": { - "autofail": "true", - "crunchy-pgbadger": "false", "crunchy-pgha-scope": "fromcrd", "deployment-name": "fromcrd", "name": "fromcrd", "pg-cluster": "fromcrd", - "pg-pod-anti-affinity": "", "pgo-version": "4.5.1", "pgouser": "pgoadmin" }, diff --git a/examples/helm/create-cluster/templates/pgcluster.yaml b/examples/helm/create-cluster/templates/pgcluster.yaml index 1a5e99617d..9d1036581d 100644 --- a/examples/helm/create-cluster/templates/pgcluster.yaml +++ b/examples/helm/create-cluster/templates/pgcluster.yaml @@ -10,7 +10,6 @@ metadata: deployment-name: {{ .Values.pgclustername }} name: {{ .Values.pgclustername }} pg-cluster: {{ .Values.pgclustername }} - pg-pod-anti-affinity: "" pgo-version: 4.5.1 pgouser: admin name: {{ .Values.pgclustername }} diff --git a/examples/kustomize/createcluster/README.md b/examples/kustomize/createcluster/README.md index ddca1fd70a..3ea4c18f9f 100644 --- a/examples/kustomize/createcluster/README.md +++ b/examples/kustomize/createcluster/README.md @@ -50,7 +50,7 @@ cluster : hippo (crunchy-postgres-ha:centos7-12.5-4.5.1) deployment : hippo deployment : hippo-backrest-shared-repo service : hippo - ClusterIP (10.0.56.86) - Ports (2022/TCP, 5432/TCP) - labels : pg-pod-anti-affinity= pgo-version=4.5.1 crunchy-postgres-exporter=false name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata autofail=true crunchy-pgbadger=false + labels : pgo-version=4.5.1 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata ``` Feel free to run other pgo cli commands on the hippo cluster @@ -87,7 +87,7 @@ cluster : dev-hippo (crunchy-postgres-ha:centos7-12.5-4.5.1) deployment : dev-hippo-pgbouncer service : dev-hippo - ClusterIP (10.0.62.87) - Ports (2022/TCP, 5432/TCP) service : dev-hippo-pgbouncer - ClusterIP (10.0.48.120) - Ports (5432/TCP) - labels : crunchy-pgha-scope=dev-hippo crunchy-postgres-exporter=false name=dev-hippo pg-cluster=dev-hippo pg-pod-anti-affinity= vendor=crunchydata autofail=true crunchy-pgbadger=false deployment-name=dev-hippo environment=development pgo-version=4.5.1 pgouser=admin + labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.5.1 pgouser=admin ``` #### staging The staging overlay will deploy a crunchy postgreSQL cluster with 2 replica's with annotations added @@ -128,7 +128,7 @@ cluster : staging-hippo (crunchy-postgres-ha:centos7-12.5-4.5.1) service : staging-hippo-replica - ClusterIP (10.0.56.57) - Ports (2022/TCP, 5432/TCP) pgreplica : staging-hippo-lnxw pgreplica : staging-hippo-rpl1 - labels : deployment-name=staging-hippo environment=staging name=staging-hippo pg-pod-anti-affinity= crunchy-postgres-exporter=false crunchy-pgbadger=false crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.5.1 pgouser=admin vendor=crunchydata autofail=true + labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.5.1 pgouser=admin vendor=crunchydata ``` #### production @@ -165,7 +165,7 @@ cluster : prod-hippo (crunchy-postgres-ha:centos7-12.5-4.5.1) service : prod-hippo - ClusterIP (10.0.56.18) - Ports (2022/TCP, 5432/TCP) service : prod-hippo-replica - ClusterIP (10.0.56.101) - Ports (2022/TCP, 5432/TCP) pgreplica : prod-hippo-flty - labels : pgo-version=4.5.1 crunchy-pgbadger=false crunchy-postgres-exporter=false deployment-name=prod-hippo environment=production pg-cluster=prod-hippo pg-pod-anti-affinity= autofail=true crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.5.1 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata ``` ### Delete the clusters To delete the clusters run the following pgo cli commands diff --git a/examples/kustomize/createcluster/base/pgcluster.yaml b/examples/kustomize/createcluster/base/pgcluster.yaml index ed7c27622d..cf3293a73a 100644 --- a/examples/kustomize/createcluster/base/pgcluster.yaml +++ b/examples/kustomize/createcluster/base/pgcluster.yaml @@ -10,7 +10,6 @@ metadata: deployment-name: hippo name: hippo pg-cluster: hippo - pg-pod-anti-affinity: "" pgo-version: 4.5.1 pgouser: admin name: hippo diff --git a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml index 33a36b5ef9..ed7d6e0b4b 100644 --- a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml +++ b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml @@ -22,6 +22,4 @@ spec: userlabels: NodeLabelKey: "" NodeLabelValue: "" - crunchy-postgres-exporter: "false" - pg-pod-anti-affinity: "" pgo-version: 4.5.1 diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json index 8499739ccf..a0645d1048 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json @@ -28,6 +28,7 @@ "name": "{{.Name}}", "vendor": "crunchydata", "pgo-pg-database": "true", + "{{.PodAntiAffinityLabelName}}": "{{.PodAntiAffinityLabelValue}}", {{.PodLabels }} } }, diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index 8307434c80..7ce3b78c11 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -827,9 +827,6 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. resp.Status.Msg = err.Error() return resp } - userLabelsMap[config.LABEL_POD_ANTI_AFFINITY] = request.PodAntiAffinity - } else { - userLabelsMap[config.LABEL_POD_ANTI_AFFINITY] = "" } // check to see if there are any pod anti-affinity overrides, specifically for diff --git a/internal/operator/cluster/clusterlogic.go b/internal/operator/cluster/clusterlogic.go index 66c109296f..964e5cb9e9 100644 --- a/internal/operator/cluster/clusterlogic.go +++ b/internal/operator/cluster/clusterlogic.go @@ -306,46 +306,49 @@ func getClusterDeploymentFields(clientset kubernetes.Interface, // create the primary deployment deploymentFields := operator.DeploymentTemplateFields{ - Name: cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY], - IsInit: true, - Replicas: "0", - ClusterName: cl.Spec.Name, - Port: cl.Spec.Port, - CCPImagePrefix: util.GetValueOrDefault(cl.Spec.CCPImagePrefix, operator.Pgo.Cluster.CCPImagePrefix), - CCPImage: cl.Spec.CCPImage, - CCPImageTag: cl.Spec.CCPImageTag, - PVCName: dataVolume.InlineVolumeSource(), - DeploymentLabels: operator.GetLabelsFromMap(cl.Spec.UserLabels), - PodAnnotations: operator.GetAnnotations(cl, crv1.ClusterAnnotationPostgres), - PodLabels: operator.GetLabelsFromMap(cl.Spec.UserLabels), - DataPathOverride: cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY], - Database: cl.Spec.Database, - SecurityContext: operator.GetPodSecurityContext(supplementalGroups), - RootSecretName: crv1.UserSecretName(cl, crv1.PGUserSuperuser), - PrimarySecretName: crv1.UserSecretName(cl, crv1.PGUserReplication), - UserSecretName: crv1.UserSecretName(cl, cl.Spec.User), - NodeSelector: operator.GetAffinity(cl.Spec.UserLabels["NodeLabelKey"], cl.Spec.UserLabels["NodeLabelValue"], "In"), - PodAntiAffinity: operator.GetPodAntiAffinity(cl, crv1.PodAntiAffinityDeploymentDefault, cl.Spec.PodAntiAffinity.Default), - ContainerResources: operator.GetResourcesJSON(cl.Spec.Resources, cl.Spec.Limits), - ConfVolume: operator.GetConfVolume(clientset, cl, namespace), - ExporterAddon: operator.GetExporterAddon(cl.Spec), - BadgerAddon: operator.GetBadgerAddon(cl, cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY]), - PgmonitorEnvVars: operator.GetPgmonitorEnvVars(cl), - ScopeLabel: config.LABEL_PGHA_SCOPE, - PgbackrestEnvVars: operator.GetPgbackrestEnvVars(cl, cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY], cl.Spec.Port), - PgbackrestS3EnvVars: operator.GetPgbackrestS3EnvVars(clientset, *cl), - ReplicaReinitOnStartFail: !operator.Pgo.Cluster.DisableReplicaStartFailReinit, - SyncReplication: operator.GetSyncReplication(cl.Spec.SyncReplication), - Tablespaces: operator.GetTablespaceNames(cl.Spec.TablespaceMounts), - TablespaceVolumes: operator.GetTablespaceVolumesJSON(cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY], tablespaceStorageTypeMap), - TablespaceVolumeMounts: operator.GetTablespaceVolumeMountsJSON(tablespaceStorageTypeMap), - TLSEnabled: cl.Spec.TLS.IsTLSEnabled(), - TLSOnly: cl.Spec.TLSOnly, - TLSSecret: cl.Spec.TLS.TLSSecret, - ReplicationTLSSecret: cl.Spec.TLS.ReplicationTLSSecret, - CASecret: cl.Spec.TLS.CASecret, - Standby: cl.Spec.Standby, - Tolerations: operator.GetTolerations(cl.Spec.Tolerations), + Name: cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY], + IsInit: true, + Replicas: "0", + ClusterName: cl.Spec.Name, + Port: cl.Spec.Port, + CCPImagePrefix: util.GetValueOrDefault(cl.Spec.CCPImagePrefix, operator.Pgo.Cluster.CCPImagePrefix), + CCPImage: cl.Spec.CCPImage, + CCPImageTag: cl.Spec.CCPImageTag, + PVCName: dataVolume.InlineVolumeSource(), + DeploymentLabels: operator.GetLabelsFromMap(cl.Spec.UserLabels), + PodAnnotations: operator.GetAnnotations(cl, crv1.ClusterAnnotationPostgres), + PodLabels: operator.GetLabelsFromMap(cl.Spec.UserLabels), + DataPathOverride: cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY], + Database: cl.Spec.Database, + SecurityContext: operator.GetPodSecurityContext(supplementalGroups), + RootSecretName: crv1.UserSecretName(cl, crv1.PGUserSuperuser), + PrimarySecretName: crv1.UserSecretName(cl, crv1.PGUserReplication), + UserSecretName: crv1.UserSecretName(cl, cl.Spec.User), + NodeSelector: operator.GetAffinity(cl.Spec.UserLabels["NodeLabelKey"], cl.Spec.UserLabels["NodeLabelValue"], "In"), + PodAntiAffinity: operator.GetPodAntiAffinity(cl, + crv1.PodAntiAffinityDeploymentDefault, cl.Spec.PodAntiAffinity.Default), + PodAntiAffinityLabelName: config.LABEL_POD_ANTI_AFFINITY, + PodAntiAffinityLabelValue: string(cl.Spec.PodAntiAffinity.Default), + ContainerResources: operator.GetResourcesJSON(cl.Spec.Resources, cl.Spec.Limits), + ConfVolume: operator.GetConfVolume(clientset, cl, namespace), + ExporterAddon: operator.GetExporterAddon(cl.Spec), + BadgerAddon: operator.GetBadgerAddon(cl, cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY]), + PgmonitorEnvVars: operator.GetPgmonitorEnvVars(cl), + ScopeLabel: config.LABEL_PGHA_SCOPE, + PgbackrestEnvVars: operator.GetPgbackrestEnvVars(cl, cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY], cl.Spec.Port), + PgbackrestS3EnvVars: operator.GetPgbackrestS3EnvVars(clientset, *cl), + ReplicaReinitOnStartFail: !operator.Pgo.Cluster.DisableReplicaStartFailReinit, + SyncReplication: operator.GetSyncReplication(cl.Spec.SyncReplication), + Tablespaces: operator.GetTablespaceNames(cl.Spec.TablespaceMounts), + TablespaceVolumes: operator.GetTablespaceVolumesJSON(cl.Annotations[config.ANNOTATION_CURRENT_PRIMARY], tablespaceStorageTypeMap), + TablespaceVolumeMounts: operator.GetTablespaceVolumeMountsJSON(tablespaceStorageTypeMap), + TLSEnabled: cl.Spec.TLS.IsTLSEnabled(), + TLSOnly: cl.Spec.TLSOnly, + TLSSecret: cl.Spec.TLS.TLSSecret, + ReplicationTLSSecret: cl.Spec.TLS.ReplicationTLSSecret, + CASecret: cl.Spec.TLS.CASecret, + Standby: cl.Spec.Standby, + Tolerations: operator.GetTolerations(cl.Spec.Tolerations), } return deploymentFields @@ -462,42 +465,45 @@ func scaleReplicaCreateDeployment(clientset kubernetes.Interface, // create the replica deployment replicaDeploymentFields := operator.DeploymentTemplateFields{ - Name: replica.Spec.Name, - ClusterName: replica.Spec.ClusterName, - Port: cluster.Spec.Port, - CCPImagePrefix: util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, operator.Pgo.Cluster.CCPImagePrefix), - CCPImageTag: imageTag, - CCPImage: image, - PVCName: dataVolume.InlineVolumeSource(), - Database: cluster.Spec.Database, - DataPathOverride: replica.Spec.Name, - Replicas: "1", - ConfVolume: operator.GetConfVolume(clientset, cluster, namespace), - DeploymentLabels: operator.GetLabelsFromMap(cluster.Spec.UserLabels), - PodAnnotations: operator.GetAnnotations(cluster, crv1.ClusterAnnotationPostgres), - PodLabels: operator.GetLabelsFromMap(cluster.Spec.UserLabels), - SecurityContext: operator.GetPodSecurityContext(supplementalGroups), - RootSecretName: crv1.UserSecretName(cluster, crv1.PGUserSuperuser), - PrimarySecretName: crv1.UserSecretName(cluster, crv1.PGUserReplication), - UserSecretName: crv1.UserSecretName(cluster, cluster.Spec.User), - ContainerResources: operator.GetResourcesJSON(cluster.Spec.Resources, cluster.Spec.Limits), - NodeSelector: operator.GetAffinity(replica.Spec.UserLabels["NodeLabelKey"], replica.Spec.UserLabels["NodeLabelValue"], "In"), - PodAntiAffinity: operator.GetPodAntiAffinity(cluster, crv1.PodAntiAffinityDeploymentDefault, cluster.Spec.PodAntiAffinity.Default), - ExporterAddon: operator.GetExporterAddon(cluster.Spec), - BadgerAddon: operator.GetBadgerAddon(cluster, replica.Spec.Name), - ScopeLabel: config.LABEL_PGHA_SCOPE, - PgbackrestEnvVars: operator.GetPgbackrestEnvVars(cluster, replica.Spec.Name, cluster.Spec.Port), - PgbackrestS3EnvVars: operator.GetPgbackrestS3EnvVars(clientset, *cluster), - ReplicaReinitOnStartFail: !operator.Pgo.Cluster.DisableReplicaStartFailReinit, - SyncReplication: operator.GetSyncReplication(cluster.Spec.SyncReplication), - Tablespaces: operator.GetTablespaceNames(cluster.Spec.TablespaceMounts), - TablespaceVolumes: operator.GetTablespaceVolumesJSON(replica.Spec.Name, tablespaceStorageTypeMap), - TablespaceVolumeMounts: operator.GetTablespaceVolumeMountsJSON(tablespaceStorageTypeMap), - TLSEnabled: cluster.Spec.TLS.IsTLSEnabled(), - TLSOnly: cluster.Spec.TLSOnly, - TLSSecret: cluster.Spec.TLS.TLSSecret, - ReplicationTLSSecret: cluster.Spec.TLS.ReplicationTLSSecret, - CASecret: cluster.Spec.TLS.CASecret, + Name: replica.Spec.Name, + ClusterName: replica.Spec.ClusterName, + Port: cluster.Spec.Port, + CCPImagePrefix: util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, operator.Pgo.Cluster.CCPImagePrefix), + CCPImageTag: imageTag, + CCPImage: image, + PVCName: dataVolume.InlineVolumeSource(), + Database: cluster.Spec.Database, + DataPathOverride: replica.Spec.Name, + Replicas: "1", + ConfVolume: operator.GetConfVolume(clientset, cluster, namespace), + DeploymentLabels: operator.GetLabelsFromMap(cluster.Spec.UserLabels), + PodAnnotations: operator.GetAnnotations(cluster, crv1.ClusterAnnotationPostgres), + PodLabels: operator.GetLabelsFromMap(cluster.Spec.UserLabels), + SecurityContext: operator.GetPodSecurityContext(supplementalGroups), + RootSecretName: crv1.UserSecretName(cluster, crv1.PGUserSuperuser), + PrimarySecretName: crv1.UserSecretName(cluster, crv1.PGUserReplication), + UserSecretName: crv1.UserSecretName(cluster, cluster.Spec.User), + ContainerResources: operator.GetResourcesJSON(cluster.Spec.Resources, cluster.Spec.Limits), + NodeSelector: operator.GetAffinity(replica.Spec.UserLabels["NodeLabelKey"], replica.Spec.UserLabels["NodeLabelValue"], "In"), + PodAntiAffinity: operator.GetPodAntiAffinity(cluster, + crv1.PodAntiAffinityDeploymentDefault, cluster.Spec.PodAntiAffinity.Default), + PodAntiAffinityLabelName: config.LABEL_POD_ANTI_AFFINITY, + PodAntiAffinityLabelValue: string(cluster.Spec.PodAntiAffinity.Default), + ExporterAddon: operator.GetExporterAddon(cluster.Spec), + BadgerAddon: operator.GetBadgerAddon(cluster, replica.Spec.Name), + ScopeLabel: config.LABEL_PGHA_SCOPE, + PgbackrestEnvVars: operator.GetPgbackrestEnvVars(cluster, replica.Spec.Name, cluster.Spec.Port), + PgbackrestS3EnvVars: operator.GetPgbackrestS3EnvVars(clientset, *cluster), + ReplicaReinitOnStartFail: !operator.Pgo.Cluster.DisableReplicaStartFailReinit, + SyncReplication: operator.GetSyncReplication(cluster.Spec.SyncReplication), + Tablespaces: operator.GetTablespaceNames(cluster.Spec.TablespaceMounts), + TablespaceVolumes: operator.GetTablespaceVolumesJSON(replica.Spec.Name, tablespaceStorageTypeMap), + TablespaceVolumeMounts: operator.GetTablespaceVolumeMountsJSON(tablespaceStorageTypeMap), + TLSEnabled: cluster.Spec.TLS.IsTLSEnabled(), + TLSOnly: cluster.Spec.TLSOnly, + TLSSecret: cluster.Spec.TLS.TLSSecret, + ReplicationTLSSecret: cluster.Spec.TLS.ReplicationTLSSecret, + CASecret: cluster.Spec.TLS.CASecret, // Give precedence to the tolerations defined on the replica spec, otherwise // take any tolerations defined on the cluster spec Tolerations: util.GetValueOrDefault( diff --git a/internal/operator/cluster/upgrade.go b/internal/operator/cluster/upgrade.go index bebdeea866..62127b687e 100644 --- a/internal/operator/cluster/upgrade.go +++ b/internal/operator/cluster/upgrade.go @@ -589,9 +589,6 @@ func preparePgclusterForUpgrade(pgcluster *crv1.Pgcluster, parameters map[string // and unable to sync all replicas to the current timeline pgcluster.Spec.DisableAutofail = false - // Don't think we'll need to do this, but leaving the comment for now.... - // pgcluster.ObjectMeta.Labels[config.LABEL_POD_ANTI_AFFINITY] = "" - // set pgouser to match the default configuration currently in use after the Operator upgrade pgcluster.ObjectMeta.Labels[config.LABEL_PGOUSER] = parameters[config.LABEL_PGOUSER] diff --git a/internal/operator/clusterutilities.go b/internal/operator/clusterutilities.go index 6497d95262..c90ea0090a 100644 --- a/internal/operator/clusterutilities.go +++ b/internal/operator/clusterutilities.go @@ -158,29 +158,31 @@ type DeploymentTemplateFields struct { DeploymentLabels string // PodAnnotations are user-specified annotations that can be applied to a // Pod, e.g. annotations specific to a PostgreSQL instance - PodAnnotations string - PodLabels string - DataPathOverride string - PVCName string - RootSecretName string - UserSecretName string - PrimarySecretName string - SecurityContext string - ContainerResources string - NodeSelector string - ConfVolume string - ExporterAddon string - BadgerAddon string - PgbackrestEnvVars string - PgbackrestS3EnvVars string - PgmonitorEnvVars string - ScopeLabel string - Replicas string - IsInit bool - ReplicaReinitOnStartFail bool - PodAntiAffinity string - SyncReplication bool - Standby bool + PodAnnotations string + PodLabels string + DataPathOverride string + PVCName string + RootSecretName string + UserSecretName string + PrimarySecretName string + SecurityContext string + ContainerResources string + NodeSelector string + ConfVolume string + ExporterAddon string + BadgerAddon string + PgbackrestEnvVars string + PgbackrestS3EnvVars string + PgmonitorEnvVars string + ScopeLabel string + Replicas string + IsInit bool + ReplicaReinitOnStartFail bool + PodAntiAffinity string + PodAntiAffinityLabelName string + PodAntiAffinityLabelValue string + SyncReplication bool + Standby bool // A comma-separated list of tablespace names...this could be an array, but // given how this would ultimately be interpreted in a shell script somewhere // down the line, it's easier for the time being to do it this way. In the From 59f89dbee02de060bf50a8d33701b6ccecbf1c83 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Thu, 31 Dec 2020 13:04:23 -0500 Subject: [PATCH 098/276] Move node affinity controls to CRD attributes Node affinity had been controlled by the use of an amorphous label on the `pgclusters.crunchydata.com` and `pgreplicas.crunchydata.com` attributes. This is now moved to use a similar approach on the CRD as used for pod anti-affinity, though using the standard Kubernetes definition for setting node affinity on a Pod. The `--node-label` syntax on the various pgo-client commands still work as expected and are mapped to using the new format. The attributes on the CRDs will allow for expansion over the number of supported node affinity rules that can be placed on a PostgreSQL cluster, particularly as Kubernetes deployment topologies continue to evolve. --- .../files/pgo-configs/affinity.json | 14 ---- .../files/pgo-configs/cluster-deployment.json | 8 ++- .../apiserver/clusterservice/clusterimpl.go | 16 +++-- .../apiserver/clusterservice/scaleimpl.go | 11 +-- internal/apiserver/common.go | 1 + internal/config/pgoconfig.go | 9 --- internal/operator/backrest/restore.go | 13 ++-- internal/operator/cluster/cluster.go | 9 ++- internal/operator/cluster/clusterlogic.go | 11 ++- internal/operator/cluster/upgrade.go | 31 +++++++++ internal/operator/clusterutilities.go | 57 +++++----------- internal/operator/pgdump/restore.go | 10 ++- internal/util/cluster.go | 25 +++++++ internal/util/cluster_test.go | 67 +++++++++++++++++++ pkg/apis/crunchydata.com/v1/cluster.go | 17 +++++ pkg/apis/crunchydata.com/v1/replica.go | 3 + .../v1/zz_generated.deepcopy.go | 32 +++++++++ 17 files changed, 245 insertions(+), 89 deletions(-) delete mode 100644 installers/ansible/roles/pgo-operator/files/pgo-configs/affinity.json create mode 100644 internal/util/cluster_test.go diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/affinity.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/affinity.json deleted file mode 100644 index a247bd9bb4..0000000000 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/affinity.json +++ /dev/null @@ -1,14 +0,0 @@ - "nodeAffinity": { - "preferredDuringSchedulingIgnoredDuringExecution": [{ - "weight": 10, - "preference": { - "matchExpressions": [{ - "key": "{{.NodeLabelKey}}", - "operator": "{{.OperatorValue}}", - "values": [ - "{{.NodeLabelValue}}" - ] - }] - } - }] - } diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json index a0645d1048..0e3f2ef6cc 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json @@ -373,9 +373,11 @@ {{.TablespaceVolumes}} ], "affinity": { - {{.NodeSelector}} - {{if and .NodeSelector .PodAntiAffinity}},{{end}} - {{.PodAntiAffinity}} + {{if .NodeSelector}} + "nodeAffinity": {{ .NodeSelector }} + {{ end }} + {{if and .NodeSelector .PodAntiAffinity}},{{end}} + {{.PodAntiAffinity}} }, "restartPolicy": "Always", "dnsPolicy": "ClusterFirst" diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index 7ce3b78c11..00b621c680 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -792,12 +792,6 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. resp.Status.Msg = err.Error() return resp } - - parts := strings.Split(request.NodeLabel, "=") - userLabelsMap[config.LABEL_NODE_LABEL_KEY] = parts[0] - userLabelsMap[config.LABEL_NODE_LABEL_VALUE] = parts[1] - - log.Debug("primary node labels used from user entered flag") } if request.ReplicaStorageConfig != "" { @@ -1242,6 +1236,16 @@ func getClusterParams(request *msgs.CreateClusterRequest, name string, userLabel spec.PodAntiAffinity = podAntiAffinity } + // if there is a node label, set the node affinity + if request.NodeLabel != "" { + nodeLabel := strings.Split(request.NodeLabel, "=") + spec.NodeAffinity = crv1.NodeAffinitySpec{ + Default: util.GenerateNodeAffinity(nodeLabel[0], []string{nodeLabel[1]}), + } + + log.Debugf("using node label %s", request.NodeLabel) + } + // if the PVCSize is overwritten, update the primary storage spec with this // value if request.PVCSize != "" { diff --git a/internal/apiserver/clusterservice/scaleimpl.go b/internal/apiserver/clusterservice/scaleimpl.go index 0a8bcf0fff..b4dc9f2dba 100644 --- a/internal/apiserver/clusterservice/scaleimpl.go +++ b/internal/apiserver/clusterservice/scaleimpl.go @@ -98,10 +98,6 @@ func ScaleCluster(request msgs.ClusterScaleRequest, pgouser string) msgs.Cluster spec.ServiceType = request.ServiceType } - // set replica node lables to blank to start with, then check for overrides - spec.UserLabels[config.LABEL_NODE_LABEL_KEY] = "" - spec.UserLabels[config.LABEL_NODE_LABEL_VALUE] = "" - // validate & parse nodeLabel if exists if request.NodeLabel != "" { if err = apiserver.ValidateNodeLabel(request.NodeLabel); err != nil { @@ -110,11 +106,10 @@ func ScaleCluster(request msgs.ClusterScaleRequest, pgouser string) msgs.Cluster return response } - parts := strings.Split(request.NodeLabel, "=") - spec.UserLabels[config.LABEL_NODE_LABEL_KEY] = parts[0] - spec.UserLabels[config.LABEL_NODE_LABEL_VALUE] = parts[1] + nodeLabel := strings.Split(request.NodeLabel, "=") + spec.NodeAffinity = util.GenerateNodeAffinity(nodeLabel[0], []string{nodeLabel[1]}) - log.Debug("using user entered node label for replica creation") + log.Debugf("using node label %s", request.NodeLabel) } labels := make(map[string]string) diff --git a/internal/apiserver/common.go b/internal/apiserver/common.go index 204f50fa70..7f5592b3c4 100644 --- a/internal/apiserver/common.go +++ b/internal/apiserver/common.go @@ -24,6 +24,7 @@ import ( "github.com/crunchydata/postgres-operator/internal/config" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" + log "github.com/sirupsen/logrus" kerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" diff --git a/internal/config/pgoconfig.go b/internal/config/pgoconfig.go index 2a72513437..09d78ab841 100644 --- a/internal/config/pgoconfig.go +++ b/internal/config/pgoconfig.go @@ -95,10 +95,6 @@ var ContainerResourcesTemplate *template.Template const containerResourcesTemplatePath = "container-resources.json" -var AffinityTemplate *template.Template - -const affinityTemplatePath = "affinity.json" - var PodAntiAffinityTemplate *template.Template const podAntiAffinityTemplatePath = "pod-anti-affinity.json" @@ -685,11 +681,6 @@ func (c *PgoConfig) GetConfig(clientset kubernetes.Interface, namespace string) return err } - AffinityTemplate, err = c.LoadTemplate(cMap, affinityTemplatePath) - if err != nil { - return err - } - PodAntiAffinityTemplate, err = c.LoadTemplate(cMap, podAntiAffinityTemplatePath) if err != nil { return err diff --git a/internal/operator/backrest/restore.go b/internal/operator/backrest/restore.go index 5d727522f6..4216a31197 100644 --- a/internal/operator/backrest/restore.go +++ b/internal/operator/backrest/restore.go @@ -26,6 +26,7 @@ import ( "github.com/crunchydata/postgres-operator/internal/config" "github.com/crunchydata/postgres-operator/internal/kubeapi" + "github.com/crunchydata/postgres-operator/internal/util" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" "github.com/crunchydata/postgres-operator/pkg/events" pgo "github.com/crunchydata/postgres-operator/pkg/generated/clientset/versioned" @@ -92,10 +93,14 @@ func UpdatePGClusterSpecForRestore(clientset kubeapi.Interface, cluster *crv1.Pg cluster.Spec.PGDataSource.RestoreOpts = restoreOpts // set the proper node affinity for the restore job - cluster.Spec.UserLabels[config.LABEL_NODE_LABEL_KEY] = - task.Spec.Parameters[config.LABEL_NODE_LABEL_KEY] - cluster.Spec.UserLabels[config.LABEL_NODE_LABEL_VALUE] = - task.Spec.Parameters[config.LABEL_NODE_LABEL_VALUE] + if task.Spec.Parameters[config.LABEL_NODE_LABEL_KEY] != "" && task.Spec.Parameters[config.LABEL_NODE_LABEL_VALUE] != "" { + cluster.Spec.NodeAffinity = crv1.NodeAffinitySpec{ + Default: util.GenerateNodeAffinity( + task.Spec.Parameters[config.LABEL_NODE_LABEL_KEY], + []string{task.Spec.Parameters[config.LABEL_NODE_LABEL_VALUE]}, + ), + } + } } // PrepareClusterForRestore prepares a PostgreSQL cluster for a restore. This includes deleting diff --git a/internal/operator/cluster/cluster.go b/internal/operator/cluster/cluster.go index 216300cc0e..9319a10c00 100644 --- a/internal/operator/cluster/cluster.go +++ b/internal/operator/cluster/cluster.go @@ -212,9 +212,12 @@ func AddClusterBase(clientset kubeapi.Interface, cl *crv1.Pgcluster, namespace s spec.UserLabels = cl.Spec.UserLabels - // the replica should not use the same node labels as the primary - spec.UserLabels[config.LABEL_NODE_LABEL_KEY] = "" - spec.UserLabels[config.LABEL_NODE_LABEL_VALUE] = "" + // if the primary cluster has default node affinity rules set, we need + // to honor them in the spec. if a different affinity is desired, the + // replica needs to set its own rules + if cl.Spec.NodeAffinity.Default != nil { + spec.NodeAffinity = cl.Spec.NodeAffinity.Default + } labels := make(map[string]string) labels[config.LABEL_PG_CLUSTER] = cl.Spec.Name diff --git a/internal/operator/cluster/clusterlogic.go b/internal/operator/cluster/clusterlogic.go index 964e5cb9e9..c7f1d4cc00 100644 --- a/internal/operator/cluster/clusterlogic.go +++ b/internal/operator/cluster/clusterlogic.go @@ -324,7 +324,7 @@ func getClusterDeploymentFields(clientset kubernetes.Interface, RootSecretName: crv1.UserSecretName(cl, crv1.PGUserSuperuser), PrimarySecretName: crv1.UserSecretName(cl, crv1.PGUserReplication), UserSecretName: crv1.UserSecretName(cl, cl.Spec.User), - NodeSelector: operator.GetAffinity(cl.Spec.UserLabels["NodeLabelKey"], cl.Spec.UserLabels["NodeLabelValue"], "In"), + NodeSelector: operator.GetNodeAffinity(cl.Spec.NodeAffinity.Default), PodAntiAffinity: operator.GetPodAntiAffinity(cl, crv1.PodAntiAffinityDeploymentDefault, cl.Spec.PodAntiAffinity.Default), PodAntiAffinityLabelName: config.LABEL_POD_ANTI_AFFINITY, @@ -463,6 +463,13 @@ func scaleReplicaCreateDeployment(clientset kubernetes.Interface, supplementalGroups = append(supplementalGroups, v.SupplementalGroups...) } + // check if there are any node affinity rules. rules on the replica supersede + // rules on the primary + nodeAffinity := cluster.Spec.NodeAffinity.Default + if replica.Spec.NodeAffinity != nil { + nodeAffinity = replica.Spec.NodeAffinity + } + // create the replica deployment replicaDeploymentFields := operator.DeploymentTemplateFields{ Name: replica.Spec.Name, @@ -484,7 +491,7 @@ func scaleReplicaCreateDeployment(clientset kubernetes.Interface, PrimarySecretName: crv1.UserSecretName(cluster, crv1.PGUserReplication), UserSecretName: crv1.UserSecretName(cluster, cluster.Spec.User), ContainerResources: operator.GetResourcesJSON(cluster.Spec.Resources, cluster.Spec.Limits), - NodeSelector: operator.GetAffinity(replica.Spec.UserLabels["NodeLabelKey"], replica.Spec.UserLabels["NodeLabelValue"], "In"), + NodeSelector: operator.GetNodeAffinity(nodeAffinity), PodAntiAffinity: operator.GetPodAntiAffinity(cluster, crv1.PodAntiAffinityDeploymentDefault, cluster.Spec.PodAntiAffinity.Default), PodAntiAffinityLabelName: config.LABEL_POD_ANTI_AFFINITY, diff --git a/internal/operator/cluster/upgrade.go b/internal/operator/cluster/upgrade.go index 62127b687e..fb5c344cdb 100644 --- a/internal/operator/cluster/upgrade.go +++ b/internal/operator/cluster/upgrade.go @@ -505,6 +505,37 @@ func preparePgclusterForUpgrade(pgcluster *crv1.Pgcluster, parameters map[string delete(pgcluster.ObjectMeta.Labels, "autofail") } + // 4.6.0 moved the node labels to the custom resource objects in a more + // structure way. if we have a node label, then let's migrate it to that + // format + if pgcluster.Spec.UserLabels["NodeLabelKey"] != "" && pgcluster.Spec.UserLabels["NodeLabelValue"] != "" { + // transition to using the native NodeAffinity objects. In the previous + // setup, this was, by default, preferred node affinity. Designed to match + // a standard setup. + requirement := v1.NodeSelectorRequirement{ + Key: pgcluster.Spec.UserLabels["NodeLabelKey"], + Values: []string{pgcluster.Spec.UserLabels["NodeLabelValue"]}, + Operator: v1.NodeSelectorOpIn, + } + term := v1.PreferredSchedulingTerm{ + Weight: crv1.NodeAffinityDefaultWeight, // taking this from the former template + Preference: v1.NodeSelectorTerm{ + MatchExpressions: []v1.NodeSelectorRequirement{requirement}, + }, + } + + // and here is our default node affinity rule + pgcluster.Spec.NodeAffinity = crv1.NodeAffinitySpec{ + Default: &v1.NodeAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: []v1.PreferredSchedulingTerm{term}, + }, + } + + // erase all trace of this + delete(pgcluster.Spec.UserLabels, "NodeLabelKey") + delete(pgcluster.Spec.UserLabels, "NodeLabelValue") + } + // 4.6.0 moved the "backrest-storage-type" label to a CRD attribute, well, // really an array of CRD attributes, which we need to map the various // attributes to. "local" will be mapped the "posix" to match the pgBackRest diff --git a/internal/operator/clusterutilities.go b/internal/operator/clusterutilities.go index c90ea0090a..fe1cb37b26 100644 --- a/internal/operator/clusterutilities.go +++ b/internal/operator/clusterutilities.go @@ -36,12 +36,6 @@ import ( "k8s.io/client-go/kubernetes" ) -// consolidate with cluster.affinityTemplateFields -const ( - AffinityInOperator = "In" - AFFINITY_NOTINOperator = "NotIn" -) - // PGHAConfigMapSuffix defines the suffix for the name of the PGHA configMap created for each PG // cluster const PGHAConfigMapSuffix = "pgha-config" @@ -74,12 +68,6 @@ const ( preferScheduleIgnoreExec affinityType = "preferredDuringSchedulingIgnoredDuringExecution" ) -type affinityTemplateFields struct { - NodeLabelKey string - NodeLabelValue string - OperatorValue string -} - type podAntiAffinityTemplateFields struct { AffinityType affinityType ClusterName string @@ -467,6 +455,24 @@ func CreatePGHAConfigMap(clientset kubernetes.Interface, cluster *crv1.Pgcluster return nil } +// GetNodeAffinity returns any node affinity rules for the Operator in a JSON +// string. If there is no data or there is an error, it will return an empty +// string. +func GetNodeAffinity(nodeAffinity *v1.NodeAffinity) string { + if nodeAffinity == nil { + return "" + } + + data, err := json.MarshalIndent(nodeAffinity, "", " ") + + if err != nil { + log.Warnf("could not generate node affinity: %s", err.Error()) + return "" + } + + return string(data) +} + // GetTablespaceNamePVCMap returns a map of the tablespace name to the PVC name func GetTablespaceNamePVCMap(clusterName string, tablespaceStorageTypeMap map[string]string) map[string]string { tablespacePVCMap := map[string]string{} @@ -633,33 +639,6 @@ func GetLabelsFromMap(labels map[string]string) string { return strings.TrimSuffix(output, ",") } -// GetAffinity ... -func GetAffinity(nodeLabelKey, nodeLabelValue string, affoperator string) string { - log.Debugf("GetAffinity with nodeLabelKey=[%s] nodeLabelKey=[%s] and operator=[%s]\n", nodeLabelKey, nodeLabelValue, affoperator) - output := "" - if nodeLabelKey == "" { - return output - } - - affinityTemplateFields := affinityTemplateFields{} - affinityTemplateFields.NodeLabelKey = nodeLabelKey - affinityTemplateFields.NodeLabelValue = nodeLabelValue - affinityTemplateFields.OperatorValue = affoperator - - var affinityDoc bytes.Buffer - err := config.AffinityTemplate.Execute(&affinityDoc, affinityTemplateFields) - if err != nil { - log.Error(err.Error()) - return output - } - - if CRUNCHY_DEBUG { - _ = config.AffinityTemplate.Execute(os.Stdout, affinityTemplateFields) - } - - return affinityDoc.String() -} - // GetPodAntiAffinity returns the populated pod anti-affinity json that should be attached to // the various pods comprising the pg cluster func GetPodAntiAffinity(cluster *crv1.Pgcluster, deploymentType crv1.PodAntiAffinityDeployment, podAntiAffinityType crv1.PodAntiAffinityType) string { diff --git a/internal/operator/pgdump/restore.go b/internal/operator/pgdump/restore.go index 95169c06de..550b64390b 100644 --- a/internal/operator/pgdump/restore.go +++ b/internal/operator/pgdump/restore.go @@ -28,8 +28,10 @@ import ( "github.com/crunchydata/postgres-operator/internal/operator/pvc" "github.com/crunchydata/postgres-operator/internal/util" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" + log "github.com/sirupsen/logrus" v1batch "k8s.io/api/batch/v1" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -76,6 +78,12 @@ func Restore(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask) { storage := cluster.Spec.PrimaryStorage taskName := task.Name + var nodeAffinity *v1.NodeAffinity + + if task.Spec.Parameters["NodeLabelKey"] != "" && task.Spec.Parameters["NodeLabelValue"] != "" { + nodeAffinity = util.GenerateNodeAffinity( + task.Spec.Parameters["NodeLabelKey"], []string{task.Spec.Parameters["NodeLabelValue"]}) + } jobFields := restorejobTemplateFields{ JobName: fmt.Sprintf("pgrestore-%s-%s", task.Spec.Parameters[config.LABEL_PGRESTORE_FROM_CLUSTER], @@ -92,7 +100,7 @@ func Restore(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask) { PITRTarget: task.Spec.Parameters[config.LABEL_PGRESTORE_PITR_TARGET], CCPImagePrefix: util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, operator.Pgo.Cluster.CCPImagePrefix), CCPImageTag: operator.Pgo.Cluster.CCPImageTag, - NodeSelector: operator.GetAffinity(task.Spec.Parameters["NodeLabelKey"], task.Spec.Parameters["NodeLabelValue"], "In"), + NodeSelector: operator.GetNodeAffinity(nodeAffinity), } var doc2 bytes.Buffer diff --git a/internal/util/cluster.go b/internal/util/cluster.go index b92ea1c587..43c52fd39b 100644 --- a/internal/util/cluster.go +++ b/internal/util/cluster.go @@ -231,6 +231,31 @@ func CreateBackrestRepoSecrets(clientset kubernetes.Interface, return err } +// GenerateNodeAffinity creates a Kubernetes node affinity object suitable for +// storage on our custom resource. For now, it only supports preferred affinity, +// though can be expanded to support more complex rules +func GenerateNodeAffinity(key string, values []string) *v1.NodeAffinity { + // generate the selector requirement, which at this point is just the + // "node label is in" requirement + requirement := v1.NodeSelectorRequirement{ + Key: key, + Values: values, + Operator: v1.NodeSelectorOpIn, + } + // build the preferred affinity term. Right now this is the only one supported + term := v1.PreferredSchedulingTerm{ + Weight: crv1.NodeAffinityDefaultWeight, + Preference: v1.NodeSelectorTerm{ + MatchExpressions: []v1.NodeSelectorRequirement{requirement}, + }, + } + + // and here is our node affinity rule + return &v1.NodeAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: []v1.PreferredSchedulingTerm{term}, + } +} + // GeneratedPasswordValidUntilDays returns the value for the number of days that // a password is valid for, which is used as part of PostgreSQL's VALID UNTIL // directive on a user. It first determines if the user provided this value via diff --git a/internal/util/cluster_test.go b/internal/util/cluster_test.go new file mode 100644 index 0000000000..3d80c561f4 --- /dev/null +++ b/internal/util/cluster_test.go @@ -0,0 +1,67 @@ +package util + +/* +Copyright 2020 Crunchy Data Solutions, Inc. +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. +*/ + +import ( + "reflect" + "testing" + + crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" + v1 "k8s.io/api/core/v1" +) + +func TestGenerateNodeAffinity(t *testing.T) { + // presently this test is really strict. as we allow for more options, we will + // need to add more tests. + t.Run("valid", func(t *testing.T) { + key := "foo" + values := []string{"bar", "baz"} + + affinity := GenerateNodeAffinity(key, values) + + if len(affinity.PreferredDuringSchedulingIgnoredDuringExecution) == 0 { + t.Fatalf("expected preferred node affinity to be set") + } else if len(affinity.PreferredDuringSchedulingIgnoredDuringExecution) > 1 { + t.Fatalf("only expected one rule to be set") + } + + term := affinity.PreferredDuringSchedulingIgnoredDuringExecution[0] + + if term.Weight != crv1.NodeAffinityDefaultWeight { + t.Fatalf("expected weight %d actual %d", crv1.NodeAffinityDefaultWeight, term.Weight) + } + + if len(term.Preference.MatchExpressions) == 0 { + t.Fatalf("expected a match expression to be set") + } else if len(term.Preference.MatchExpressions) > 1 { + t.Fatalf("expected only one match expression to be set") + } + + rule := term.Preference.MatchExpressions[0] + + if rule.Operator != v1.NodeSelectorOpIn { + t.Fatalf("operator expected %s actual %s", v1.NodeSelectorOpIn, rule.Operator) + } + + if rule.Key != key { + t.Fatalf("key expected %s actual %s", key, rule.Key) + } + + if !reflect.DeepEqual(rule.Values, values) { + t.Fatalf("values expected %v actual %v", values, rule.Values) + } + }) +} diff --git a/pkg/apis/crunchydata.com/v1/cluster.go b/pkg/apis/crunchydata.com/v1/cluster.go index 76f7ccae67..54c7c8fe4c 100644 --- a/pkg/apis/crunchydata.com/v1/cluster.go +++ b/pkg/apis/crunchydata.com/v1/cluster.go @@ -117,6 +117,7 @@ type PgclusterSpec struct { Status string `json:"status"` CustomConfig string `json:"customconfig"` UserLabels map[string]string `json:"userlabels"` + NodeAffinity NodeAffinitySpec `json:"nodeAffinity"` PodAntiAffinity PodAntiAffinitySpec `json:"podAntiAffinity"` SyncReplication *bool `json:"syncReplication"` BackrestConfig []v1.VolumeProjection `json:"backrestConfig"` @@ -248,6 +249,22 @@ type PgclusterStatus struct { // swagger:ignore type PgclusterState string +// NodeAffinityDefaultWeight is the default weighting for the preferred node +// affinity. This was taken from our legacy template for handling this, so there +// may be some logic to this, or this could be an arbitrary weight. Either way, +// the number needs to be somewhere between [1, 100]. +const NodeAffinityDefaultWeight int32 = 10 + +// NodeAffinitySpec contains optional NodeAffinity rules for the different +// deployment types managed by the Operator. While similar to how the Operator +// handles pod anti-affinity, makes reference to the supported Kubernetes +// objects to maintain more familiarity and consistency. +// +// All of these are optional, so one must ensure they check for nils. +type NodeAffinitySpec struct { + Default *v1.NodeAffinity `json:"default"` +} + // PodAntiAffinityDeployment distinguishes between the different types of // Deployments that can leverage PodAntiAffinity type PodAntiAffinityDeployment int diff --git a/pkg/apis/crunchydata.com/v1/replica.go b/pkg/apis/crunchydata.com/v1/replica.go index 45f6cd123a..08a830a8d3 100644 --- a/pkg/apis/crunchydata.com/v1/replica.go +++ b/pkg/apis/crunchydata.com/v1/replica.go @@ -46,6 +46,9 @@ type PgreplicaSpec struct { ServiceType v1.ServiceType `json:"serviceType"` Status string `json:"status"` UserLabels map[string]string `json:"userlabels"` + // NodeAffinity is an optional structure that dictates how an instance should + // be deployed in an environment + NodeAffinity *v1.NodeAffinity `json:"nodeAffinity"` // Tolerations are an optional list of Pod toleration rules that are applied // to the PostgreSQL instance. Tolerations []v1.Toleration `json:"tolerations"` diff --git a/pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go b/pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go index 3cef8c84f5..86b9b5ed4d 100644 --- a/pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go +++ b/pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go @@ -68,6 +68,27 @@ func (in *ClusterAnnotations) DeepCopy() *ClusterAnnotations { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeAffinitySpec) DeepCopyInto(out *NodeAffinitySpec) { + *out = *in + if in.Default != nil { + in, out := &in.Default, &out.Default + *out = new(corev1.NodeAffinity) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeAffinitySpec. +func (in *NodeAffinitySpec) DeepCopy() *NodeAffinitySpec { + if in == nil { + return nil + } + out := new(NodeAffinitySpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PGDataSourceSpec) DeepCopyInto(out *PGDataSourceSpec) { *out = *in @@ -248,6 +269,7 @@ func (in *PgclusterSpec) DeepCopyInto(out *PgclusterSpec) { (*out)[key] = val } } + in.NodeAffinity.DeepCopyInto(&out.NodeAffinity) out.PodAntiAffinity = in.PodAntiAffinity if in.SyncReplication != nil { in, out := &in.SyncReplication, &out.SyncReplication @@ -261,6 +283,11 @@ func (in *PgclusterSpec) DeepCopyInto(out *PgclusterSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.BackrestStorageTypes != nil { + in, out := &in.BackrestStorageTypes, &out.BackrestStorageTypes + *out = make([]BackrestStorageType, len(*in)) + copy(*out, *in) + } if in.TablespaceMounts != nil { in, out := &in.TablespaceMounts, &out.TablespaceMounts *out = make(map[string]PgStorageSpec, len(*in)) @@ -472,6 +499,11 @@ func (in *PgreplicaSpec) DeepCopyInto(out *PgreplicaSpec) { (*out)[key] = val } } + if in.NodeAffinity != nil { + in, out := &in.NodeAffinity, &out.NodeAffinity + *out = new(corev1.NodeAffinity) + (*in).DeepCopyInto(*out) + } if in.Tolerations != nil { in, out := &in.Tolerations, &out.Tolerations *out = make([]corev1.Toleration, len(*in)) From 9818ac9eadd3af90ba30db070195941cd0fdef4b Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Thu, 31 Dec 2020 16:03:42 -0500 Subject: [PATCH 099/276] Allow for selection of node affinity type from pgo client The previous commit allows for node affinity type (e.g. preferred, required) to be selected directly from a custom resource. This is now possible from the command line using the `--node-affinity-type` flag, which accepts values of "preferred" and "required" ("prefer" and "require" are also accepted). This flag is available on the following commands: - `pgo create cluster` - `pgo scale` - `pgo restore` Note that this flag needs to be used in conjunction with the `--node-label` flag. --- cmd/pgo/cmd/cluster.go | 1 + cmd/pgo/cmd/common.go | 33 +++++++++++ cmd/pgo/cmd/create.go | 6 ++ cmd/pgo/cmd/restore.go | 6 +- cmd/pgo/cmd/scale.go | 19 ++++--- .../architecture/high-availability/_index.md | 31 ++++++++-- docs/content/custom-resources/_index.md | 22 +++++-- .../reference/pgo_create_cluster.md | 11 ++-- .../pgo-client/reference/pgo_restore.md | 7 ++- .../reference/pgo_update_cluster.md | 10 ++-- .../overlay/staging/hippo-rpl1-pgreplica.yaml | 2 - .../apiserver/backrestservice/backrestimpl.go | 6 ++ .../apiserver/clusterservice/clusterimpl.go | 2 +- .../apiserver/clusterservice/scaleimpl.go | 2 +- .../apiserver/pgdumpservice/pgdumpimpl.go | 6 ++ internal/config/labels.go | 29 +++++----- internal/operator/backrest/restore.go | 6 ++ internal/operator/pgdump/restore.go | 7 ++- internal/util/cluster.go | 33 +++++++---- internal/util/cluster_test.go | 57 +++++++++++++++++-- pkg/apis/crunchydata.com/v1/cluster.go | 10 ++++ pkg/apiservermsgs/backrestmsgs.go | 15 +++-- pkg/apiservermsgs/clustermsgs.go | 10 +++- pkg/apiservermsgs/pgdumpmsgs.go | 13 +++-- 24 files changed, 270 insertions(+), 74 deletions(-) diff --git a/cmd/pgo/cmd/cluster.go b/cmd/pgo/cmd/cluster.go index fd2f1241ab..9d0465da22 100644 --- a/cmd/pgo/cmd/cluster.go +++ b/cmd/pgo/cmd/cluster.go @@ -260,6 +260,7 @@ func createCluster(args []string, ns string, createClusterCmd *cobra.Command) { r.Name = args[0] r.Namespace = ns r.ReplicaCount = ClusterReplicaCount + r.NodeAffinityType = getNodeAffinityType(NodeLabel, NodeAffinityType) r.NodeLabel = NodeLabel r.PasswordLength = PasswordLength r.PasswordSuperuser = PasswordSuperuser diff --git a/cmd/pgo/cmd/common.go b/cmd/pgo/cmd/common.go index f1e8f84e70..6d618e44bc 100644 --- a/cmd/pgo/cmd/common.go +++ b/cmd/pgo/cmd/common.go @@ -18,7 +18,10 @@ package cmd import ( "encoding/json" "fmt" + "os" "reflect" + + crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" ) // unitType is used to group together the unit types @@ -98,6 +101,36 @@ func getMaxLength(results []interface{}, title, fieldName string) int { return maxLength + 1 } +// getNodeAffinityType takes a string value of "NodeAffinityType" and converts +// it to the proper enumeration +func getNodeAffinityType(nodeLabel, nodeAffinityType string) crv1.NodeAffinityType { + // if nodeAffinityType is not set, just exit with the default + if nodeAffinityType == "" { + return crv1.NodeAffinityTypePreferred + } + + // force an exit if nodeAffinityType is set but nodeLabel is not + if nodeLabel == "" && nodeAffinityType != "" { + fmt.Println("error: --node-affinity-type set, but --node-label not set") + os.Exit(1) + } + + // and away we go + switch nodeAffinityType { + default: + fmt.Printf("error: invalid node affinity type %q. choices are: preferred required\n", nodeAffinityType) + os.Exit(1) + case "preferred", "prefer": + return crv1.NodeAffinityTypePreferred + case "required", "require": + return crv1.NodeAffinityTypeRequired + } + + // one should never get to here because of the exit, but we need to compile + // the program. Yes, we really shouldn't be exiting. + return crv1.NodeAffinityTypePreferred +} + // getSizeAndUnit determines the best size to return based on the best unit // where unit is KB, MB, GB, etc... func getSizeAndUnit(size int64) (float64, unitType) { diff --git a/cmd/pgo/cmd/create.go b/cmd/pgo/cmd/create.go index 6b798e4a23..19d1612f1a 100644 --- a/cmd/pgo/cmd/create.go +++ b/cmd/pgo/cmd/create.go @@ -109,6 +109,10 @@ var BackrestS3CASecretName string // BackrestRepoPath allows the pgBackRest repo path to be defined instead of using the default var BackrestRepoPath string +// NodeAffinityType needs to be used with "NodeLabel" and can be one of +// "preferred" or "required" -- gets mapped to an enumeration +var NodeAffinityType string + // Standby determines whether or not the cluster should be created as a standby cluster var Standby bool @@ -395,6 +399,8 @@ func init() { "the Crunchy Postgres Exporter sidecar container. Defaults to server value (24Mi).") createClusterCmd.Flags().StringVar(&ExporterMemoryLimit, "exporter-memory-limit", "", "Set the amount of memory to limit for "+ "the Crunchy Postgres Exporter sidecar container.") + createClusterCmd.Flags().StringVar(&NodeAffinityType, "node-affinity-type", "", "Sets the type of node affinity to use. "+ + "Can be either preferred (default) or required. Must be used with --node-label") createClusterCmd.Flags().StringVarP(&NodeLabel, "node-label", "", "", "The node label (key=value) to use in placing the primary database. If not set, any node is used.") createClusterCmd.Flags().StringVarP(&Password, "password", "", "", "The password to use for standard user account created during cluster initialization.") createClusterCmd.Flags().IntVarP(&PasswordLength, "password-length", "", 0, "If no password is supplied, sets the length of the automatically generated password. Defaults to the value set on the server.") diff --git a/cmd/pgo/cmd/restore.go b/cmd/pgo/cmd/restore.go index aa3b4bb725..bb8931f5b7 100644 --- a/cmd/pgo/cmd/restore.go +++ b/cmd/pgo/cmd/restore.go @@ -64,13 +64,15 @@ func init() { restoreCmd.Flags().StringVarP(&BackupOpts, "backup-opts", "", "", "The restore options for pgbackrest or pgdump.") restoreCmd.Flags().StringVarP(&PITRTarget, "pitr-target", "", "", "The PITR target, being a PostgreSQL timestamp such as '2018-08-13 11:25:42.582117-04'.") + restoreCmd.Flags().StringVar(&NodeAffinityType, "node-affinity-type", "", "Sets the type of node affinity to use. "+ + "Can be either preferred (default) or required. Must be used with --node-label") restoreCmd.Flags().StringVarP(&NodeLabel, "node-label", "", "", "The node label (key=value) to use when scheduling "+ "the restore job, and in the case of a pgBackRest restore, also the new (i.e. restored) primary deployment. If not set, any node is used.") restoreCmd.Flags().BoolVar(&NoPrompt, "no-prompt", false, "No command line confirmation.") restoreCmd.Flags().StringVarP(&BackupPVC, "backup-pvc", "", "", "The PVC containing the pgdump to restore from.") restoreCmd.Flags().StringVarP(&PGDumpDB, "pgdump-database", "d", "postgres", "The name of the database pgdump will restore.") restoreCmd.Flags().StringVarP(&BackupType, "backup-type", "", "", "The type of backup to restore from, default is pgbackrest. Valid types are pgbackrest or pgdump.") - restoreCmd.Flags().StringVarP(&BackrestStorageType, "pgbackrest-storage-type", "", "", "The type of storage to use for a pgBackRest restore. Either \"local\", \"s3\". (default \"local\")") + restoreCmd.Flags().StringVarP(&BackrestStorageType, "pgbackrest-storage-type", "", "", "The type of storage to use for a pgBackRest restore. Either \"posix\", \"s3\". (default \"posix\")") } // restore .... @@ -90,6 +92,7 @@ func restore(args []string, ns string) { request.PITRTarget = PITRTarget request.FromPVC = BackupPVC // use PVC specified on command line for pgrestore request.PGDumpDB = PGDumpDB + request.NodeAffinityType = getNodeAffinityType(NodeLabel, NodeAffinityType) request.NodeLabel = NodeLabel response, err = api.RestoreDump(httpclient, &SessionCredentials, request) @@ -101,6 +104,7 @@ func restore(args []string, ns string) { request.RestoreOpts = BackupOpts request.PITRTarget = PITRTarget request.NodeLabel = NodeLabel + request.NodeAffinityType = getNodeAffinityType(NodeLabel, NodeAffinityType) request.BackrestStorageType = BackrestStorageType response, err = api.Restore(httpclient, &SessionCredentials, request) diff --git a/cmd/pgo/cmd/scale.go b/cmd/pgo/cmd/scale.go index 5303c6dd6a..dd9d8a8a95 100644 --- a/cmd/pgo/cmd/scale.go +++ b/cmd/pgo/cmd/scale.go @@ -59,6 +59,8 @@ func init() { scaleCmd.Flags().StringVarP(&ServiceType, "service-type", "", "", "The service type to use in the replica Service. If not set, the default in pgo.yaml will be used.") scaleCmd.Flags().StringVarP(&CCPImageTag, "ccp-image-tag", "", "", "The CCPImageTag to use for cluster creation. If specified, overrides the .pgo.yaml setting.") + scaleCmd.Flags().StringVar(&NodeAffinityType, "node-affinity-type", "", "Sets the type of node affinity to use. "+ + "Can be either preferred (default) or required. Must be used with --node-label") scaleCmd.Flags().StringVarP(&NodeLabel, "node-label", "", "", "The node label (key) to use in placing the replica database. If not set, any node is used.") scaleCmd.Flags().BoolVar(&NoPrompt, "no-prompt", false, "No command line confirmation.") scaleCmd.Flags().IntVarP(&ReplicaCount, "replica-count", "", 1, "The replica count to apply to the clusters.") @@ -72,14 +74,15 @@ func init() { func scaleCluster(args []string, ns string) { for _, arg := range args { request := msgs.ClusterScaleRequest{ - CCPImageTag: CCPImageTag, - Name: arg, - Namespace: ns, - NodeLabel: NodeLabel, - ReplicaCount: ReplicaCount, - ServiceType: v1.ServiceType(ServiceType), - StorageConfig: StorageConfig, - Tolerations: getClusterTolerations(Tolerations), + CCPImageTag: CCPImageTag, + Name: arg, + Namespace: ns, + NodeAffinityType: getNodeAffinityType(NodeLabel, NodeAffinityType), + NodeLabel: NodeLabel, + ReplicaCount: ReplicaCount, + ServiceType: v1.ServiceType(ServiceType), + StorageConfig: StorageConfig, + Tolerations: getClusterTolerations(Tolerations), } response, err := api.ScaleCluster(httpclient, &SessionCredentials, request) diff --git a/docs/content/architecture/high-availability/_index.md b/docs/content/architecture/high-availability/_index.md index 3a5d79c806..f267d306f0 100644 --- a/docs/content/architecture/high-availability/_index.md +++ b/docs/content/architecture/high-availability/_index.md @@ -272,10 +272,33 @@ when creating a PostgreSQL cluster; pgo create cluster thatcluster --node-label=region=us-east-1 ``` -The Node Affinity only uses the `preferred` scheduling strategy (similar to what -is described in the Pod Anti-Affinity section above), so if a Pod cannot be -scheduled to a particular Node matching the label, it will be scheduled to a -different Node. +By default, node affinity uses the `preferred` scheduling strategy (similar to +what is described in the [Pod Anti-Affinity]("#how-the-crunchy-postgresql-operator-uses-pod-anti-affinity") +section above), so if a Pod cannot be scheduled to a particular Node matching +the label, it will be scheduled to a different Node. + +The PostgreSQL Operator supports two different types of node affinity: + +- `preferred` +- `required` + +which can be selected with the `--node-affinity-type` flag, e.g: + +``` +pgo create cluster hippo \ + --node-label=region=us-east-1 --node-affinity-type=required +``` + +When creating a cluster, the node affinity rules will be applied to the primary +and any other PostgreSQL instances that are added. If you would like to specify +a node affinity rule for a specific instance, you can do so with the +[`pgo scale`]({{< relref "pgo-client/reference/pgo_scale.md">}}) command and the +`--node-label` and `--node-affinity-type` flags, i.e: + +``` +pgo scale cluster hippo \ + --node-label=region=us-south-1 --node-affinity-type=required +``` ## Tolerations diff --git a/docs/content/custom-resources/_index.md b/docs/content/custom-resources/_index.md index abc7706284..84589f100e 100644 --- a/docs/content/custom-resources/_index.md +++ b/docs/content/custom-resources/_index.md @@ -543,8 +543,6 @@ spec: supplementalgroups: "" tolerations: [] userlabels: - NodeLabelKey: "" - NodeLabelValue: "" pgo-version: {{< param operatorVersion >}} EOF @@ -748,6 +746,7 @@ make changes, as described below. | Limits | `create`, `update` | Specify the container resource limits that the PostgreSQL cluster should use. Follows the [Kubernetes definitions of resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | | Name | `create` | The name of the PostgreSQL instance that is the primary. On creation, this should be set to be the same as `ClusterName`. | | Namespace | `create` | The Kubernetes Namespace that the PostgreSQL cluster is deployed in. | +| NodeAffinity | `create` | Sets the [node affinity rules]({{< relref "/architecture/high-availability/_index.md#node-affinity" >}}) for the PostgreSQL cluster and associated PostgreSQL instances. Can be overridden on a per-instance (`pgreplicas.crunchydata.com`) basis. Please see the `Node Affinity Specification` section below. | | PGBadger | `create`,`update` | If `true`, deploys the `crunchy-pgbadger` sidecar for query analysis. | | PGBadgerPort | `create` | If the `PGBadger` label is set, then this specifies the port that the pgBadger sidecar runs on (e.g. `10000`) | | PGDataSource | `create` | Used to indicate if a PostgreSQL cluster should bootstrap its data from a pgBackRest repository. This uses the PostgreSQL Data Source Specification, described below. | @@ -762,7 +761,7 @@ make changes, as described below. | ServiceType | `create`, `update` | Sets the Kubernetes [Service](https://kubernetes.io/docs/concepts/services-networking/service/) type to use for the cluster. If not set, defaults to `ClusterIP`. | | SyncReplication | `create` | If set to `true`, specifies the PostgreSQL cluster to use [synchronous replication]({{< relref "/architecture/high-availability/_index.md#how-the-crunchy-postgresql-operator-uses-pod-anti-affinity#synchronous-replication-guarding-against-transactions-loss" >}}).| | User | `create` | The name of the PostgreSQL user that is created when the PostgreSQL cluster is first created. | -| UserLabels | `create` | A set of key-value string pairs that are used as a sort of "catch-all" for things that really should be modeled in the CRD. These values do get copied to the actually CR labels. If you want to set up metrics collection or pgBadger, you would specify `"crunchy-postgres-exporter": "true"` and `"crunchy-pgbadger": "true"` here, respectively. However, this structure does need to be set, so just follow whatever is in the example. | +| UserLabels | `create` | A set of key-value string pairs that are used as a sort of "catch-all" as well as a way to add custom labels to clusters. This will disappear at some point. | | TablespaceMounts | `create`,`update` | Lists any tablespaces that are attached to the PostgreSQL cluster. Tablespaces can be added at a later time by updating the `TablespaceMounts` entry, but they cannot be removed. Stores a map of information, with the key being the name of the tablespace, and the value being a Storage Specification, defined below. | | TLS | `create` | Defines the attributes for enabling TLS for a PostgreSQL cluster. See TLS Specification below. | | TLSOnly | `create` | If set to true, requires client connections to use only TLS to connect to the PostgreSQL database. | @@ -787,6 +786,20 @@ attribute and how it works. | StorageType | `create` | Set to `create` if storage is provisioned (e.g. using `hostpath`). Set to `dynamic` if using a dynamic storage provisioner, e.g. via a `StorageClass`. | | SupplementalGroups | `create` | If provided, a comma-separated list of group IDs to use in case it is needed to interface with a particular storage system. Typically used with NFS or hostpath storage. | +##### Node Affinity Specification + +Sets the [node affinity]({{< relref "/architecture/high-availability/_index.md#node-affinity" >}}) +for the PostgreSQL cluster and associated deployments. Follows the [Kubernetes standard format for setting node affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity), including `preferred` and `required` node affinity. + +To set node affinity for a PostgreSQL cluster, you will need to modify the `default` attribute in the node affinity specification. As mentioned above, the values that `default` accepts match what Kubernetes uses for [node affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity). + +For a detailed explanation for node affinity works. Please see the [high-availability]({{< relref "/architecture/high-availability/_index.md#node-affinity" >}}) +documentation. + +| Attribute | Action | Description | +|-----------|--------|-------------| +| default | `create` | The default pod anti-affinity to use for all PostgreSQL instances managed in a given PostgreSQL cluster. Can be overridden on a per-instance basis with the `pgreplicas.crunchydata.com` custom resource. | + ##### Pod Anti-Affinity Specification Sets the [pod anti-affinity]({{< relref "/architecture/high-availability/_index.md#how-the-crunchy-postgresql-operator-uses-pod-anti-affinity" >}}) @@ -878,6 +891,7 @@ cluster. All of the attributes only affect the replica when it is created. | ClusterName | `create` | The name of the PostgreSQL cluster, e.g. `hippo`. This is used to group PostgreSQL instances (primary, replicas) together. | | Name | `create` | The name of this PostgreSQL replica. It should be unique within a `ClusterName`. | | Namespace | `create` | The Kubernetes Namespace that the PostgreSQL cluster is deployed in. | +| NodeAffinity | `create` | Sets the [node affinity rules]({{< relref "/architecture/high-availability/_index.md#node-affinity" >}}) for this PostgreSQL instance. Follows the [Kubernetes standard format for setting node affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity). | | ReplicaStorage | `create` | A specification that gives information about the storage attributes for any replicas in the PostgreSQL cluster. For details, please see the `Storage Specification` section in the `pgclusters.crunchydata.com` description. This will likely be changed in the future based on the nature of the high-availability system, but presently it is still required that you set it. It is recommended you use similar settings to that of `PrimaryStorage`. | -| UserLabels | `create` | A set of key-value string pairs that are used as a sort of "catch-all" for things that really should be modeled in the CRD. These values do get copied to the actually CR labels. If you want to set up metrics collection, you would specify `"crunchy-postgres-exporter": "true"` here. This also allows for node selector pinning using `NodeLabelKey` and `NodeLabelValue`. However, this structure does need to be set, so just follow whatever is in the example. | +| UserLabels | `create` | A set of key-value string pairs that are used as a sort of "catch-all" as well as a way to add custom labels to clusters. This will disappear at some point. | | Tolerations | `create`,`update` | Any array of Kubernetes [Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/). Please refer to the [Kubernetes documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) for how to set this field. | diff --git a/docs/content/pgo-client/reference/pgo_create_cluster.md b/docs/content/pgo-client/reference/pgo_create_cluster.md index 36000d4253..bc5dd28f99 100644 --- a/docs/content/pgo-client/reference/pgo_create_cluster.md +++ b/docs/content/pgo-client/reference/pgo_create_cluster.md @@ -21,7 +21,7 @@ pgo create cluster [flags] --annotation strings Add an Annotation to all of the managed deployments (PostgreSQL, pgBackRest, pgBouncer) The format to add an annotation is "name=value" The format to remove an annotation is "name-" - + For example, to add two annotations: "--annotation=hippo=awesome,elephant=cool" --annotation-pgbackrest strings Add an Annotation specifically to pgBackRest deployments The format to add an annotation is "name=value" @@ -49,6 +49,7 @@ pgo create cluster [flags] --memory string Set the amount of RAM to request, e.g. 1GiB. Overrides the default server value. --memory-limit string Set the amount of RAM to limit, e.g. 1GiB. --metrics Adds the crunchy-postgres-exporter container to the database pod. + --node-affinity-type string Sets the type of node affinity to use. Can be either preferred (default) or required. Must be used with --node-label --node-label string The node label (key=value) to use in placing the primary database. If not set, any node is used. --password string The password to use for standard user account created during cluster initialization. --password-length int If no password is supplied, sets the length of the automatically generated password. Defaults to the value set on the server. @@ -100,13 +101,13 @@ pgo create cluster [flags] --storage-config string The name of a Storage config in pgo.yaml to use for the cluster storage. --sync-replication Enables synchronous replication for the cluster. --tablespace strings Create a PostgreSQL tablespace on the cluster, e.g. "name=ts1:storageconfig=nfsstorage". The format is a key/value map that is delimited by "=" and separated by ":". The following parameters are available: - + - name (required): the name of the PostgreSQL tablespace - storageconfig (required): the storage configuration to use, as specified in the list available in the "pgo-config" ConfigMap (aka "pgo.yaml") - pvcsize: the size of the PVC capacity, which overrides the value set in the specified storageconfig. Follows the Kubernetes quantity format. - + For example, to create a tablespace with the NFS storage configuration with a PVC of size 10GiB: - + --tablespace=name=ts1:storageconfig=nfsstorage:pvcsize=10Gi --tls-only If true, forces all PostgreSQL connections to be over TLS. Must also set "server-tls-secret" and "server-ca-secret" --toleration strings Set Pod tolerations for each PostgreSQL instance in a cluster. @@ -134,4 +135,4 @@ pgo create cluster [flags] * [pgo create](/pgo-client/reference/pgo_create/) - Create a Postgres Operator resource -###### Auto generated by spf13/cobra on 24-Dec-2020 +###### Auto generated by spf13/cobra on 31-Dec-2020 diff --git a/docs/content/pgo-client/reference/pgo_restore.md b/docs/content/pgo-client/reference/pgo_restore.md index e7e377c914..78d3584e7e 100644 --- a/docs/content/pgo-client/reference/pgo_restore.md +++ b/docs/content/pgo-client/reference/pgo_restore.md @@ -9,7 +9,7 @@ Perform a restore from previous backup RESTORE performs a restore to a new PostgreSQL cluster. This includes stopping the database and recreating a new primary with the restored data. Valid backup types to restore from are pgbackrest and pgdump. For example: - pgo restore mycluster + pgo restore mycluster ``` pgo restore [flags] @@ -23,6 +23,7 @@ pgo restore [flags] --backup-type string The type of backup to restore from, default is pgbackrest. Valid types are pgbackrest or pgdump. -h, --help help for restore --no-prompt No command line confirmation. + --node-affinity-type string Sets the type of node affinity to use. Can be either preferred (default) or required. Must be used with --node-label --node-label string The node label (key=value) to use when scheduling the restore job, and in the case of a pgBackRest restore, also the new (i.e. restored) primary deployment. If not set, any node is used. --pgbackrest-storage-type string The type of storage to use for a pgBackRest restore. Either "posix", "s3". (default "posix") -d, --pgdump-database string The name of the database pgdump will restore. (default "postgres") @@ -32,7 +33,7 @@ pgo restore [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -46,4 +47,4 @@ pgo restore [flags] * [pgo](/pgo-client/reference/pgo/) - The pgo command line interface. -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 31-Dec-2020 diff --git a/docs/content/pgo-client/reference/pgo_update_cluster.md b/docs/content/pgo-client/reference/pgo_update_cluster.md index 4622833460..08b1e5ae4c 100644 --- a/docs/content/pgo-client/reference/pgo_update_cluster.md +++ b/docs/content/pgo-client/reference/pgo_update_cluster.md @@ -25,7 +25,7 @@ pgo update cluster [flags] --annotation strings Add an Annotation to all of the managed deployments (PostgreSQL, pgBackRest, pgBouncer) The format to add an annotation is "name=value" The format to remove an annotation is "name-" - + For example, to add two annotations: "--annotation=hippo=awesome,elephant=cool" --annotation-pgbackrest strings Add an Annotation specifically to pgBackRest deployments The format to add an annotation is "name=value" @@ -62,13 +62,13 @@ pgo update cluster [flags] --shutdown Shutdown the database cluster if it is currently running. --startup Restart the database cluster if it is currently shutdown. --tablespace strings Add a PostgreSQL tablespace on the cluster, e.g. "name=ts1:storageconfig=nfsstorage". The format is a key/value map that is delimited by "=" and separated by ":". The following parameters are available: - + - name (required): the name of the PostgreSQL tablespace - storageconfig (required): the storage configuration to use, as specified in the list available in the "pgo-config" ConfigMap (aka "pgo.yaml") - pvcsize: the size of the PVC capacity, which overrides the value set in the specified storageconfig. Follows the Kubernetes quantity format. - + For example, to create a tablespace with the NFS storage configuration with a PVC of size 10GiB: - + --tablespace=name=ts1:storageconfig=nfsstorage:pvcsize=10Gi ``` @@ -89,4 +89,4 @@ pgo update cluster [flags] * [pgo update](/pgo-client/reference/pgo_update/) - Update a pgouser, pgorole, or cluster -###### Auto generated by spf13/cobra on 28-Dec-2020 +###### Auto generated by spf13/cobra on 31-Dec-2020 diff --git a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml index ed7d6e0b4b..253995eb69 100644 --- a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml +++ b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml @@ -20,6 +20,4 @@ spec: storagetype: dynamic supplementalgroups: "" userlabels: - NodeLabelKey: "" - NodeLabelValue: "" pgo-version: 4.5.1 diff --git a/internal/apiserver/backrestservice/backrestimpl.go b/internal/apiserver/backrestservice/backrestimpl.go index e743925d95..c9ae9fb943 100644 --- a/internal/apiserver/backrestservice/backrestimpl.go +++ b/internal/apiserver/backrestservice/backrestimpl.go @@ -626,6 +626,12 @@ func getRestoreParams(cluster *crv1.Pgcluster, request *msgs.RestoreRequest) (*c spec.Parameters[config.LABEL_NODE_LABEL_KEY] = parts[0] spec.Parameters[config.LABEL_NODE_LABEL_VALUE] = parts[1] + // determine if any special node affinity type must be set + spec.Parameters[config.LABEL_NODE_AFFINITY_TYPE] = "preferred" + if request.NodeAffinityType == crv1.NodeAffinityTypeRequired { + spec.Parameters[config.LABEL_NODE_AFFINITY_TYPE] = "required" + } + log.Debug("Restore node labels used from user entered flag") } diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index 00b621c680..784426d834 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -1240,7 +1240,7 @@ func getClusterParams(request *msgs.CreateClusterRequest, name string, userLabel if request.NodeLabel != "" { nodeLabel := strings.Split(request.NodeLabel, "=") spec.NodeAffinity = crv1.NodeAffinitySpec{ - Default: util.GenerateNodeAffinity(nodeLabel[0], []string{nodeLabel[1]}), + Default: util.GenerateNodeAffinity(request.NodeAffinityType, nodeLabel[0], []string{nodeLabel[1]}), } log.Debugf("using node label %s", request.NodeLabel) diff --git a/internal/apiserver/clusterservice/scaleimpl.go b/internal/apiserver/clusterservice/scaleimpl.go index b4dc9f2dba..36739d1e44 100644 --- a/internal/apiserver/clusterservice/scaleimpl.go +++ b/internal/apiserver/clusterservice/scaleimpl.go @@ -107,7 +107,7 @@ func ScaleCluster(request msgs.ClusterScaleRequest, pgouser string) msgs.Cluster } nodeLabel := strings.Split(request.NodeLabel, "=") - spec.NodeAffinity = util.GenerateNodeAffinity(nodeLabel[0], []string{nodeLabel[1]}) + spec.NodeAffinity = util.GenerateNodeAffinity(request.NodeAffinityType, nodeLabel[0], []string{nodeLabel[1]}) log.Debugf("using node label %s", request.NodeLabel) } diff --git a/internal/apiserver/pgdumpservice/pgdumpimpl.go b/internal/apiserver/pgdumpservice/pgdumpimpl.go index 5a93045493..3e97ede60a 100644 --- a/internal/apiserver/pgdumpservice/pgdumpimpl.go +++ b/internal/apiserver/pgdumpservice/pgdumpimpl.go @@ -456,6 +456,12 @@ func buildPgTaskForRestore(taskName string, action string, request *msgs.PgResto spec.Parameters[config.LABEL_NODE_LABEL_KEY] = parts[0] spec.Parameters[config.LABEL_NODE_LABEL_VALUE] = parts[1] + // determine if any special node affinity type must be set + spec.Parameters[config.LABEL_NODE_AFFINITY_TYPE] = "preferred" + if request.NodeAffinityType == crv1.NodeAffinityTypeRequired { + spec.Parameters[config.LABEL_NODE_AFFINITY_TYPE] = "required" + } + log.Debug("Restore node labels used from user entered flag") } diff --git a/internal/config/labels.go b/internal/config/labels.go index 1d0f9b7c3f..5dce3957f0 100644 --- a/internal/config/labels.go +++ b/internal/config/labels.go @@ -38,20 +38,21 @@ const ( ) const ( - LABEL_PGPOLICY = "pgpolicy" - LABEL_INGEST = "ingest" - LABEL_PGREMOVE = "pgremove" - LABEL_PVCNAME = "pvcname" - LABEL_EXPORTER = "crunchy-postgres-exporter" - LABEL_ARCHIVE = "archive" - LABEL_ARCHIVE_TIMEOUT = "archive-timeout" - LABEL_NODE_LABEL_KEY = "NodeLabelKey" - LABEL_NODE_LABEL_VALUE = "NodeLabelValue" - LABEL_REPLICA_NAME = "replica-name" - LABEL_CCP_IMAGE_TAG_KEY = "ccp-image-tag" - LABEL_CCP_IMAGE_KEY = "ccp-image" - LABEL_IMAGE_PREFIX = "image-prefix" - LABEL_POD_ANTI_AFFINITY = "pg-pod-anti-affinity" + LABEL_PGPOLICY = "pgpolicy" + LABEL_INGEST = "ingest" + LABEL_PGREMOVE = "pgremove" + LABEL_PVCNAME = "pvcname" + LABEL_EXPORTER = "crunchy-postgres-exporter" + LABEL_ARCHIVE = "archive" + LABEL_ARCHIVE_TIMEOUT = "archive-timeout" + LABEL_NODE_AFFINITY_TYPE = "node-affinity-type" + LABEL_NODE_LABEL_KEY = "NodeLabelKey" + LABEL_NODE_LABEL_VALUE = "NodeLabelValue" + LABEL_REPLICA_NAME = "replica-name" + LABEL_CCP_IMAGE_TAG_KEY = "ccp-image-tag" + LABEL_CCP_IMAGE_KEY = "ccp-image" + LABEL_IMAGE_PREFIX = "image-prefix" + LABEL_POD_ANTI_AFFINITY = "pg-pod-anti-affinity" ) const ( diff --git a/internal/operator/backrest/restore.go b/internal/operator/backrest/restore.go index 4216a31197..a8395b2484 100644 --- a/internal/operator/backrest/restore.go +++ b/internal/operator/backrest/restore.go @@ -94,8 +94,14 @@ func UpdatePGClusterSpecForRestore(clientset kubeapi.Interface, cluster *crv1.Pg // set the proper node affinity for the restore job if task.Spec.Parameters[config.LABEL_NODE_LABEL_KEY] != "" && task.Spec.Parameters[config.LABEL_NODE_LABEL_VALUE] != "" { + affinityType := crv1.NodeAffinityTypePreferred + if task.Spec.Parameters[config.LABEL_NODE_AFFINITY_TYPE] == "required" { + affinityType = crv1.NodeAffinityTypeRequired + } + cluster.Spec.NodeAffinity = crv1.NodeAffinitySpec{ Default: util.GenerateNodeAffinity( + affinityType, task.Spec.Parameters[config.LABEL_NODE_LABEL_KEY], []string{task.Spec.Parameters[config.LABEL_NODE_LABEL_VALUE]}, ), diff --git a/internal/operator/pgdump/restore.go b/internal/operator/pgdump/restore.go index 550b64390b..38b118c35b 100644 --- a/internal/operator/pgdump/restore.go +++ b/internal/operator/pgdump/restore.go @@ -81,7 +81,12 @@ func Restore(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask) { var nodeAffinity *v1.NodeAffinity if task.Spec.Parameters["NodeLabelKey"] != "" && task.Spec.Parameters["NodeLabelValue"] != "" { - nodeAffinity = util.GenerateNodeAffinity( + affinityType := crv1.NodeAffinityTypePreferred + if task.Spec.Parameters[config.LABEL_NODE_AFFINITY_TYPE] == "required" { + affinityType = crv1.NodeAffinityTypeRequired + } + + nodeAffinity = util.GenerateNodeAffinity(affinityType, task.Spec.Parameters["NodeLabelKey"], []string{task.Spec.Parameters["NodeLabelValue"]}) } diff --git a/internal/util/cluster.go b/internal/util/cluster.go index 43c52fd39b..ef7796f439 100644 --- a/internal/util/cluster.go +++ b/internal/util/cluster.go @@ -234,7 +234,8 @@ func CreateBackrestRepoSecrets(clientset kubernetes.Interface, // GenerateNodeAffinity creates a Kubernetes node affinity object suitable for // storage on our custom resource. For now, it only supports preferred affinity, // though can be expanded to support more complex rules -func GenerateNodeAffinity(key string, values []string) *v1.NodeAffinity { +func GenerateNodeAffinity(affinityType crv1.NodeAffinityType, key string, values []string) *v1.NodeAffinity { + nodeAffinity := &v1.NodeAffinity{} // generate the selector requirement, which at this point is just the // "node label is in" requirement requirement := v1.NodeSelectorRequirement{ @@ -242,18 +243,30 @@ func GenerateNodeAffinity(key string, values []string) *v1.NodeAffinity { Values: values, Operator: v1.NodeSelectorOpIn, } - // build the preferred affinity term. Right now this is the only one supported - term := v1.PreferredSchedulingTerm{ - Weight: crv1.NodeAffinityDefaultWeight, - Preference: v1.NodeSelectorTerm{ + + // build out the node affinity based on whether or not this is required or + // preferred (the default) + if affinityType == crv1.NodeAffinityTypeRequired { + // build the required affinity term. + nodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution = &v1.NodeSelector{ + NodeSelectorTerms: make([]v1.NodeSelectorTerm, 1), + } + nodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0] = v1.NodeSelectorTerm{ MatchExpressions: []v1.NodeSelectorRequirement{requirement}, - }, + } + } else { + // build the preferred affinity term. + term := v1.PreferredSchedulingTerm{ + Weight: crv1.NodeAffinityDefaultWeight, + Preference: v1.NodeSelectorTerm{ + MatchExpressions: []v1.NodeSelectorRequirement{requirement}, + }, + } + nodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution = []v1.PreferredSchedulingTerm{term} } - // and here is our node affinity rule - return &v1.NodeAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []v1.PreferredSchedulingTerm{term}, - } + // return the node affinity rule + return nodeAffinity } // GeneratedPasswordValidUntilDays returns the value for the number of days that diff --git a/internal/util/cluster_test.go b/internal/util/cluster_test.go index 3d80c561f4..bf50277c8f 100644 --- a/internal/util/cluster_test.go +++ b/internal/util/cluster_test.go @@ -24,13 +24,18 @@ import ( ) func TestGenerateNodeAffinity(t *testing.T) { - // presently this test is really strict. as we allow for more options, we will - // need to add more tests. - t.Run("valid", func(t *testing.T) { + // presently only one rule is allowed, so we are testing for that. future + // tests may need to expand upon that + t.Run("preferred", func(t *testing.T) { + affinityType := crv1.NodeAffinityTypePreferred key := "foo" values := []string{"bar", "baz"} - affinity := GenerateNodeAffinity(key, values) + affinity := GenerateNodeAffinity(affinityType, key, values) + + if affinity.RequiredDuringSchedulingIgnoredDuringExecution != nil { + t.Fatalf("expected required node affinity to not be set") + } if len(affinity.PreferredDuringSchedulingIgnoredDuringExecution) == 0 { t.Fatalf("expected preferred node affinity to be set") @@ -64,4 +69,48 @@ func TestGenerateNodeAffinity(t *testing.T) { t.Fatalf("values expected %v actual %v", values, rule.Values) } }) + + t.Run("required", func(t *testing.T) { + affinityType := crv1.NodeAffinityTypeRequired + key := "foo" + values := []string{"bar", "baz"} + + affinity := GenerateNodeAffinity(affinityType, key, values) + + if len(affinity.PreferredDuringSchedulingIgnoredDuringExecution) != 0 { + t.Fatalf("expected preferred node affinity to not be set") + } + + if affinity.RequiredDuringSchedulingIgnoredDuringExecution == nil { + t.Fatalf("expected required node affinity to be set") + } + + if len(affinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms) == 0 { + t.Fatalf("expected required node affinity to have at least one rule.") + } else if len(affinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms) > 1 { + t.Fatalf("expected required node affinity to have only one rule.") + } + + term := affinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0] + + if len(term.MatchExpressions) == 0 { + t.Fatalf("expected a match expression to be set") + } else if len(term.MatchExpressions) > 1 { + t.Fatalf("expected only one match expression to be set") + } + + rule := term.MatchExpressions[0] + + if rule.Operator != v1.NodeSelectorOpIn { + t.Fatalf("operator expected %s actual %s", v1.NodeSelectorOpIn, rule.Operator) + } + + if rule.Key != key { + t.Fatalf("key expected %s actual %s", key, rule.Key) + } + + if !reflect.DeepEqual(rule.Values, values) { + t.Fatalf("values expected %v actual %v", values, rule.Values) + } + }) } diff --git a/pkg/apis/crunchydata.com/v1/cluster.go b/pkg/apis/crunchydata.com/v1/cluster.go index 54c7c8fe4c..1eade8a99f 100644 --- a/pkg/apis/crunchydata.com/v1/cluster.go +++ b/pkg/apis/crunchydata.com/v1/cluster.go @@ -265,6 +265,16 @@ type NodeAffinitySpec struct { Default *v1.NodeAffinity `json:"default"` } +// NodeAffinityType indicates the type of node affinity that the request seeks +// to use. Given the custom resource uses the native Kubernetes types to set +// node affinity, this is just for convenience for the API +type NodeAffinityType int + +const ( + NodeAffinityTypePreferred NodeAffinityType = iota + NodeAffinityTypeRequired +) + // PodAntiAffinityDeployment distinguishes between the different types of // Deployments that can leverage PodAntiAffinity type PodAntiAffinityDeployment int diff --git a/pkg/apiservermsgs/backrestmsgs.go b/pkg/apiservermsgs/backrestmsgs.go index b1c4887fa9..29acd33a99 100644 --- a/pkg/apiservermsgs/backrestmsgs.go +++ b/pkg/apiservermsgs/backrestmsgs.go @@ -15,6 +15,10 @@ See the License for the specific language governing permissions and limitations under the License. */ +import ( + crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" +) + // CreateBackrestBackupResponse ... // swagger:model type CreateBackrestBackupResponse struct { @@ -145,10 +149,13 @@ type RestoreResponse struct { // RestoreRequest ... // swagger:model type RestoreRequest struct { - Namespace string - FromCluster string - RestoreOpts string - PITRTarget string + Namespace string + FromCluster string + RestoreOpts string + PITRTarget string + // NodeAffinityType is only considered when "NodeLabel" is also set, and is + // either a value of "preferred" (default) or "required" + NodeAffinityType crv1.NodeAffinityType NodeLabel string BackrestStorageType string } diff --git a/pkg/apiservermsgs/clustermsgs.go b/pkg/apiservermsgs/clustermsgs.go index 61255f6cc8..ee2cb8aff0 100644 --- a/pkg/apiservermsgs/clustermsgs.go +++ b/pkg/apiservermsgs/clustermsgs.go @@ -46,8 +46,11 @@ type ShowClusterRequest struct { // // swagger:model type CreateClusterRequest struct { - Name string `json:"Name"` - Namespace string + Name string `json:"Name"` + Namespace string + // NodeAffinityType is only considered when "NodeLabel" is also set, and is + // either a value of "preferred" (default) or "required" + NodeAffinityType crv1.NodeAffinityType NodeLabel string PasswordLength int PasswordSuperuser string @@ -565,6 +568,9 @@ type ClusterScaleRequest struct { Name string `json:"name"` // Namespace is the namespace in which the queried cluster resides. Namespace string `json:"namespace"` + // NodeAffinityType is only considered when "NodeLabel" is also set, and is + // either a value of "preferred" (default) or "required" + NodeAffinityType crv1.NodeAffinityType // NodeLabel if provided is a node label to use. NodeLabel string `json:"nodeLabel"` // ReplicaCount is the number of replicas to add to the cluster. This is diff --git a/pkg/apiservermsgs/pgdumpmsgs.go b/pkg/apiservermsgs/pgdumpmsgs.go index e247fca304..3afb00955a 100644 --- a/pkg/apiservermsgs/pgdumpmsgs.go +++ b/pkg/apiservermsgs/pgdumpmsgs.go @@ -1,9 +1,5 @@ package apiservermsgs -import ( - crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" -) - /* Copyright 2019 - 2020 Crunchy Data Solutions, Inc. Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,6 +15,10 @@ See the License for the specific language governing permissions and limitations under the License. */ +import ( + crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" +) + // CreatepgDumpBackupResponse ... // swagger:model type CreatepgDumpBackupResponse struct { @@ -61,7 +61,10 @@ type PgRestoreRequest struct { PGDumpDB string RestoreOpts string PITRTarget string - NodeLabel string + // NodeAffinityType is only considered when "NodeLabel" is also set, and is + // either a value of "preferred" (default) or "required" + NodeAffinityType crv1.NodeAffinityType + NodeLabel string } // NOTE: these are ported over from legacy functionality From e6cbc1efff6089068f1ad3a62e45d20a9c0378e2 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Fri, 1 Jan 2021 14:01:04 -0500 Subject: [PATCH 100/276] Update build dependencies This updates the build dependencies to the following versions: - Kubernetes: 0.20.1 - controller-runtime: 0.6.4 --- go.mod | 10 ++++----- go.sum | 65 ++++++++++++++++++++++++++++++++++++---------------------- 2 files changed, 46 insertions(+), 29 deletions(-) diff --git a/go.mod b/go.mod index e85242cd76..c2142d2be5 100644 --- a/go.mod +++ b/go.mod @@ -18,11 +18,11 @@ require ( go.opentelemetry.io/otel v0.13.0 go.opentelemetry.io/otel/exporters/stdout v0.13.0 go.opentelemetry.io/otel/exporters/trace/jaeger v0.13.0 - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 + golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 - k8s.io/api v0.19.4 - k8s.io/apimachinery v0.19.4 - k8s.io/client-go v0.19.4 - sigs.k8s.io/controller-runtime v0.6.3 + k8s.io/api v0.20.1 + k8s.io/apimachinery v0.20.1 + k8s.io/client-go v0.20.1 + sigs.k8s.io/controller-runtime v0.6.4 sigs.k8s.io/yaml v1.2.0 ) diff --git a/go.sum b/go.sum index 36a21fea4b..41050fa343 100644 --- a/go.sum +++ b/go.sum @@ -6,7 +6,6 @@ cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxK cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= @@ -33,17 +32,22 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest v0.9.6/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= +github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= -github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= -github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/sketches-go v0.0.1 h1:RtG+76WKgZuz6FIaGsjoPePmadDBkuD/KC6+ZWu78b8= @@ -118,6 +122,7 @@ github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= @@ -213,6 +218,8 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -243,6 +250,8 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= @@ -449,10 +458,11 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -523,6 +533,8 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgN golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -584,11 +596,12 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4 h1:5/PjkGUjvEU5Gl6BxmvKRPpqo2uNMv4rcHBMwzk/st8= -golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f h1:Fqb3ao1hUmOR3GkUOg/Y+BadLwykBIzs5q8Ez2SbHyc= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY= +golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -596,11 +609,15 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -773,17 +790,17 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI= -k8s.io/api v0.19.4 h1:I+1I4cgJYuCDgiLNjKx7SLmIbwgj9w7N7Zr5vSIdwpo= -k8s.io/api v0.19.4/go.mod h1:SbtJ2aHCItirzdJ36YslycFNzWADYH3tgOhvBEFtZAk= +k8s.io/api v0.20.1 h1:ud1c3W3YNzGd6ABJlbFfKXBKXO+1KdGfcgGGNgFR03E= +k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/apiextensions-apiserver v0.18.6 h1:vDlk7cyFsDyfwn2rNAO2DbmUbvXy5yT5GE3rrqOzaMo= k8s.io/apiextensions-apiserver v0.18.6/go.mod h1:lv89S7fUysXjLZO7ke783xOwVTm6lKizADfvUM/SS/M= k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= -k8s.io/apimachinery v0.19.4 h1:+ZoddM7nbzrDCp0T3SWnyxqf8cbWPT2fkZImoyvHUG0= -k8s.io/apimachinery v0.19.4/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= +k8s.io/apimachinery v0.20.1 h1:LAhz8pKbgR8tUwn7boK+b2HZdt7MiTu2mkYtFMUjTRQ= +k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apiserver v0.18.6/go.mod h1:Zt2XvTHuaZjBz6EFYzpp+X4hTmgWGy8AthNVnTdm3Wg= k8s.io/client-go v0.18.6/go.mod h1:/fwtGLjYMS1MaM5oi+eXhKwG+1UHidUEXRh6cNsdO0Q= -k8s.io/client-go v0.19.4 h1:85D3mDNoLF+xqpyE9Dh/OtrJDyJrSRKkHmDXIbEzer8= -k8s.io/client-go v0.19.4/go.mod h1:ZrEy7+wj9PjH5VMBCuu/BDlvtUAku0oVFk4MmnW9mWA= +k8s.io/client-go v0.20.1 h1:Qquik0xNFbK9aUG92pxHYsyfea5/RPO9o9bSywNor+M= +k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/code-generator v0.18.6/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= k8s.io/component-base v0.18.6/go.mod h1:knSVsibPR5K6EW2XOjEHik6sdU5nCvKMrzMt2D4In14= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= @@ -795,25 +812,25 @@ k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0 h1:Foj74zO6RbjjP4hBEKjnYtjjAhGg4jNynUdYF6fJrok= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.4.0 h1:7+X0fUguPyrKEC4WjH8iGDg3laWgMo5tMnRTIGTTxGQ= +k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= -k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6 h1:+WnxoVtG8TMiudHBSEtrVL1egv36TkkJm+bA8AxicmQ= -k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= +k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd h1:sOHNzJIkytDF6qadMNKhhDRpc6ODik8lVC6nOur7B2c= +k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20200729134348-d5654de09c73 h1:uJmqzgNWG7XyClnU/mLPBWwfKKF1K8Hf8whTseBgJcg= -k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920 h1:CbnUZsM497iRC5QMVkHwyl8s2tB3g7yaSHkYPkpgelw= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= -sigs.k8s.io/controller-runtime v0.6.3 h1:SBbr+inLPEKhvlJtrvDcwIpm+uhDvp63Bl72xYJtoOE= -sigs.k8s.io/controller-runtime v0.6.3/go.mod h1:WlZNXcM0++oyaQt4B7C2lEE5JYRs8vJUzRP4N4JpdAY= +sigs.k8s.io/controller-runtime v0.6.4 h1:4013CKsBs5bEqo+LevzDett+LLxag/FjQWG94nVZ/9g= +sigs.k8s.io/controller-runtime v0.6.4/go.mod h1:WlZNXcM0++oyaQt4B7C2lEE5JYRs8vJUzRP4N4JpdAY= sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= -sigs.k8s.io/structured-merge-diff/v4 v4.0.1 h1:YXTMot5Qz/X1iBRJhAt+vI+HVttY0WkSqqhKxQ0xVbA= -sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2 h1:YHQV7Dajm86OuqnIR6zAelnDWBRjo+YhYV9PmGrh1s8= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= From bd046f684a9933d3276bb240d7a2c63e3f65d4cc Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Fri, 1 Jan 2021 14:11:49 -0500 Subject: [PATCH 101/276] Bump pgMonitor to v4.4 --- bin/get-pgmonitor.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/get-pgmonitor.sh b/bin/get-pgmonitor.sh index cfc178e77d..efa62b8628 100755 --- a/bin/get-pgmonitor.sh +++ b/bin/get-pgmonitor.sh @@ -14,7 +14,7 @@ # limitations under the License. echo "Getting pgMonitor..." -PGMONITOR_COMMIT='v4.4-RC7' +PGMONITOR_COMMIT='v4.4' # pgMonitor Setup if [[ -d ${PGOROOT?}/tools/pgmonitor ]] From a65ccf78a8c5ff0a068cffb49d5ae220fa2e8da4 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Fri, 1 Jan 2021 14:28:08 -0500 Subject: [PATCH 102/276] Add `make check` target for running tests --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 5aa5bae77d..3c41592987 100644 --- a/Makefile +++ b/Makefile @@ -201,6 +201,9 @@ pgo-base-docker: pgo-base-build #======== Utility ======= +check: + PGOROOT=$(PGOROOT) go test ./... + cli-docs: rm docs/content/pgo-client/reference/*.md cd docs/content/pgo-client/reference && go run ../../../../cmd/pgo/generatedocs.go From b7012d041754218fccea63b7a23cf802bc5c5cd1 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sat, 2 Jan 2021 10:45:36 -0500 Subject: [PATCH 103/276] Remove current bash completion support This has been unmaintained and seemingly unused. A future release could reintroduce it. --- Makefile | 1 - cmd/pgo/cmd/root.go | 15 - docs/content/pgo-client/_index.md | 1 - examples/pgo-bash-completion | 2146 ----------------------------- 4 files changed, 2163 deletions(-) delete mode 100644 examples/pgo-bash-completion diff --git a/Makefile b/Makefile index 3c41592987..5f5e8f06e6 100644 --- a/Makefile +++ b/Makefile @@ -246,7 +246,6 @@ release: linuxpgo macpgo winpgo cp bin/pgo $(RELTMPDIR) cp bin/pgo-mac $(RELTMPDIR) cp bin/pgo.exe $(RELTMPDIR) - cp $(PGOROOT)/examples/pgo-bash-completion $(RELTMPDIR) tar czvf $(RELFILE) -C $(RELTMPDIR) . generate: diff --git a/cmd/pgo/cmd/root.go b/cmd/pgo/cmd/root.go index 3cd416c784..f22a92fb1a 100644 --- a/cmd/pgo/cmd/root.go +++ b/cmd/pgo/cmd/root.go @@ -104,19 +104,4 @@ func initConfig() { httpclient = hc } } - - if os.Getenv("GENERATE_BASH_COMPLETION") != "" { - generateBashCompletion() - } -} - -func generateBashCompletion() { - log.Debugf("generating bash completion script") - // #nosec: G303 - file, err2 := os.Create("/tmp/pgo-bash-completion.out") - if err2 != nil { - fmt.Println("Error: ", err2.Error()) - } - defer file.Close() - _ = RootCmd.GenBashCompletion(file) } diff --git a/docs/content/pgo-client/_index.md b/docs/content/pgo-client/_index.md index f1c6149c3f..f5fb7c7d3f 100644 --- a/docs/content/pgo-client/_index.md +++ b/docs/content/pgo-client/_index.md @@ -154,7 +154,6 @@ client. | Name | Description | | :-- | :-- | | `EXCLUDE_OS_TRUST` | Exclude CA certs from OS default trust store. | -| `GENERATE_BASH_COMPLETION` | If set, will allow `pgo` to leverage "bash completion" to help complete commands as they are typed. | | `PGO_APISERVER_URL` | The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a `/`. | | `PGO_CA_CERT` | The CA certificate file path for authenticating to the PostgreSQL Operator apiserver. | | `PGO_CLIENT_CERT` | The client certificate file path for authenticating to the PostgreSQL Operator apiserver. | diff --git a/examples/pgo-bash-completion b/examples/pgo-bash-completion deleted file mode 100644 index 6c89ce0b37..0000000000 --- a/examples/pgo-bash-completion +++ /dev/null @@ -1,2146 +0,0 @@ -# bash completion for pgo -*- shell-script -*- - -__pgo_debug() -{ - if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then - echo "$*" >> "${BASH_COMP_DEBUG_FILE}" - fi -} - -# Homebrew on Macs have version 1.3 of bash-completion which doesn't include -# _init_completion. This is a very minimal version of that function. -__pgo_init_completion() -{ - COMPREPLY=() - _get_comp_words_by_ref "$@" cur prev words cword -} - -__pgo_index_of_word() -{ - local w word=$1 - shift - index=0 - for w in "$@"; do - [[ $w = "$word" ]] && return - index=$((index+1)) - done - index=-1 -} - -__pgo_contains_word() -{ - local w word=$1; shift - for w in "$@"; do - [[ $w = "$word" ]] && return - done - return 1 -} - -__pgo_handle_reply() -{ - __pgo_debug "${FUNCNAME[0]}" - case $cur in - -*) - if [[ $(type -t compopt) = "builtin" ]]; then - compopt -o nospace - fi - local allflags - if [ ${#must_have_one_flag[@]} -ne 0 ]; then - allflags=("${must_have_one_flag[@]}") - else - allflags=("${flags[*]} ${two_word_flags[*]}") - fi - COMPREPLY=( $(compgen -W "${allflags[*]}" -- "$cur") ) - if [[ $(type -t compopt) = "builtin" ]]; then - [[ "${COMPREPLY[0]}" == *= ]] || compopt +o nospace - fi - - # complete after --flag=abc - if [[ $cur == *=* ]]; then - if [[ $(type -t compopt) = "builtin" ]]; then - compopt +o nospace - fi - - local index flag - flag="${cur%=*}" - __pgo_index_of_word "${flag}" "${flags_with_completion[@]}" - COMPREPLY=() - if [[ ${index} -ge 0 ]]; then - PREFIX="" - cur="${cur#*=}" - ${flags_completion[${index}]} - if [ -n "${ZSH_VERSION}" ]; then - # zsh completion needs --flag= prefix - eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )" - fi - fi - fi - return 0; - ;; - esac - - # check if we are handling a flag with special work handling - local index - __pgo_index_of_word "${prev}" "${flags_with_completion[@]}" - if [[ ${index} -ge 0 ]]; then - ${flags_completion[${index}]} - return - fi - - # we are parsing a flag and don't have a special handler, no completion - if [[ ${cur} != "${words[cword]}" ]]; then - return - fi - - local completions - completions=("${commands[@]}") - if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then - completions=("${must_have_one_noun[@]}") - fi - if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then - completions+=("${must_have_one_flag[@]}") - fi - COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") ) - - if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then - COMPREPLY=( $(compgen -W "${noun_aliases[*]}" -- "$cur") ) - fi - - if [[ ${#COMPREPLY[@]} -eq 0 ]]; then - declare -F __custom_func >/dev/null && __custom_func - fi - - # available in bash-completion >= 2, not always present on macOS - if declare -F __ltrim_colon_completions >/dev/null; then - __ltrim_colon_completions "$cur" - fi - - # If there is only 1 completion and it is a flag with an = it will be completed - # but we don't want a space after the = - if [[ "${#COMPREPLY[@]}" -eq "1" ]] && [[ $(type -t compopt) = "builtin" ]] && [[ "${COMPREPLY[0]}" == --*= ]]; then - compopt -o nospace - fi -} - -# The arguments should be in the form "ext1|ext2|extn" -__pgo_handle_filename_extension_flag() -{ - local ext="$1" - _filedir "@(${ext})" -} - -__pgo_handle_subdirs_in_dir_flag() -{ - local dir="$1" - pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 -} - -__pgo_handle_flag() -{ - __pgo_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" - - # if a command required a flag, and we found it, unset must_have_one_flag() - local flagname=${words[c]} - local flagvalue - # if the word contained an = - if [[ ${words[c]} == *"="* ]]; then - flagvalue=${flagname#*=} # take in as flagvalue after the = - flagname=${flagname%=*} # strip everything after the = - flagname="${flagname}=" # but put the = back - fi - __pgo_debug "${FUNCNAME[0]}: looking for ${flagname}" - if __pgo_contains_word "${flagname}" "${must_have_one_flag[@]}"; then - must_have_one_flag=() - fi - - # if you set a flag which only applies to this command, don't show subcommands - if __pgo_contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then - commands=() - fi - - # keep flag value with flagname as flaghash - # flaghash variable is an associative array which is only supported in bash > 3. - if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then - if [ -n "${flagvalue}" ] ; then - flaghash[${flagname}]=${flagvalue} - elif [ -n "${words[ $((c+1)) ]}" ] ; then - flaghash[${flagname}]=${words[ $((c+1)) ]} - else - flaghash[${flagname}]="true" # pad "true" for bool flag - fi - fi - - # skip the argument to a two word flag - if __pgo_contains_word "${words[c]}" "${two_word_flags[@]}"; then - c=$((c+1)) - # if we are looking for a flags value, don't show commands - if [[ $c -eq $cword ]]; then - commands=() - fi - fi - - c=$((c+1)) - -} - -__pgo_handle_noun() -{ - __pgo_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" - - if __pgo_contains_word "${words[c]}" "${must_have_one_noun[@]}"; then - must_have_one_noun=() - elif __pgo_contains_word "${words[c]}" "${noun_aliases[@]}"; then - must_have_one_noun=() - fi - - nouns+=("${words[c]}") - c=$((c+1)) -} - -__pgo_handle_command() -{ - __pgo_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" - - local next_command - if [[ -n ${last_command} ]]; then - next_command="_${last_command}_${words[c]//:/__}" - else - if [[ $c -eq 0 ]]; then - next_command="_pgo_root_command" - else - next_command="_${words[c]//:/__}" - fi - fi - c=$((c+1)) - __pgo_debug "${FUNCNAME[0]}: looking for ${next_command}" - declare -F "$next_command" >/dev/null && $next_command -} - -__pgo_handle_word() -{ - if [[ $c -ge $cword ]]; then - __pgo_handle_reply - return - fi - __pgo_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" - if [[ "${words[c]}" == -* ]]; then - __pgo_handle_flag - elif __pgo_contains_word "${words[c]}" "${commands[@]}"; then - __pgo_handle_command - elif [[ $c -eq 0 ]]; then - __pgo_handle_command - elif __pgo_contains_word "${words[c]}" "${command_aliases[@]}"; then - # aliashash variable is an associative array which is only supported in bash > 3. - if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then - words[c]=${aliashash[${words[c]}]} - __pgo_handle_command - else - __pgo_handle_noun - fi - else - __pgo_handle_noun - fi - __pgo_handle_word -} - -_pgo_apply() -{ - last_command="pgo_apply" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--dry-run") - local_nonpersistent_flags+=("--dry-run") - flags+=("--selector=") - two_word_flags+=("-s") - local_nonpersistent_flags+=("--selector=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_backup() -{ - last_command="pgo_backup" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--backup-opts=") - local_nonpersistent_flags+=("--backup-opts=") - flags+=("--backup-type=") - local_nonpersistent_flags+=("--backup-type=") - flags+=("--pgbackrest-storage-type=") - local_nonpersistent_flags+=("--pgbackrest-storage-type=") - flags+=("--pvc-name=") - local_nonpersistent_flags+=("--pvc-name=") - flags+=("--selector=") - two_word_flags+=("-s") - local_nonpersistent_flags+=("--selector=") - flags+=("--storage-config=") - local_nonpersistent_flags+=("--storage-config=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_cat() -{ - last_command="pgo_cat" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_create_cluster() -{ - last_command="pgo_create_cluster" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--autofail") - local_nonpersistent_flags+=("--autofail") - flags+=("--ccp-image=") - local_nonpersistent_flags+=("--ccp-image=") - flags+=("--ccp-image-tag=") - two_word_flags+=("-c") - local_nonpersistent_flags+=("--ccp-image-tag=") - flags+=("--custom-config=") - local_nonpersistent_flags+=("--custom-config=") - flags+=("--labels=") - two_word_flags+=("-l") - local_nonpersistent_flags+=("--labels=") - flags+=("--metrics") - local_nonpersistent_flags+=("--metrics") - flags+=("--node-label=") - local_nonpersistent_flags+=("--node-label=") - flags+=("--password=") - two_word_flags+=("-w") - local_nonpersistent_flags+=("--password=") - flags+=("--pgbackrest=") - local_nonpersistent_flags+=("--pgbackrest=") - flags+=("--pgbackrest-storage-type=") - local_nonpersistent_flags+=("--pgbackrest-storage-type=") - flags+=("--pgbadger") - local_nonpersistent_flags+=("--pgbadger") - flags+=("--pgbouncer") - local_nonpersistent_flags+=("--pgbouncer") - flags+=("--policies=") - two_word_flags+=("-z") - local_nonpersistent_flags+=("--policies=") - flags+=("--replica-count=") - local_nonpersistent_flags+=("--replica-count=") - flags+=("--replica-storage-config=") - local_nonpersistent_flags+=("--replica-storage-config=") - flags+=("--secret-from=") - two_word_flags+=("-s") - local_nonpersistent_flags+=("--secret-from=") - two_word_flags+=("-e") - flags+=("--service-type=") - local_nonpersistent_flags+=("--service-type=") - flags+=("--storage-config=") - local_nonpersistent_flags+=("--storage-config=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_create_namespace() -{ - last_command="pgo_create_namespace" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_create_pgbouncer() -{ - last_command="pgo_create_pgbouncer" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--selector=") - two_word_flags+=("-s") - local_nonpersistent_flags+=("--selector=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_create_pgorole() -{ - last_command="pgo_create_pgorole" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--permissions=") - local_nonpersistent_flags+=("--permissions=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_create_pgouser() -{ - last_command="pgo_create_pgouser" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--all-namespaces") - local_nonpersistent_flags+=("--all-namespaces") - flags+=("--pgouser-namespaces=") - local_nonpersistent_flags+=("--pgouser-namespaces=") - flags+=("--pgouser-password=") - local_nonpersistent_flags+=("--pgouser-password=") - flags+=("--pgouser-roles=") - local_nonpersistent_flags+=("--pgouser-roles=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_create_policy() -{ - last_command="pgo_create_policy" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--in-file=") - two_word_flags+=("-i") - local_nonpersistent_flags+=("--in-file=") - flags+=("--url=") - two_word_flags+=("-u") - local_nonpersistent_flags+=("--url=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_create_schedule() -{ - last_command="pgo_create_schedule" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--ccp-image-tag=") - two_word_flags+=("-c") - local_nonpersistent_flags+=("--ccp-image-tag=") - flags+=("--database=") - local_nonpersistent_flags+=("--database=") - flags+=("--pgbackrest-backup-type=") - local_nonpersistent_flags+=("--pgbackrest-backup-type=") - flags+=("--pgbackrest-storage-type=") - local_nonpersistent_flags+=("--pgbackrest-storage-type=") - flags+=("--policy=") - local_nonpersistent_flags+=("--policy=") - flags+=("--pvc-name=") - local_nonpersistent_flags+=("--pvc-name=") - flags+=("--schedule=") - local_nonpersistent_flags+=("--schedule=") - flags+=("--schedule-opts=") - local_nonpersistent_flags+=("--schedule-opts=") - flags+=("--schedule-type=") - local_nonpersistent_flags+=("--schedule-type=") - flags+=("--secret=") - local_nonpersistent_flags+=("--secret=") - flags+=("--selector=") - two_word_flags+=("-s") - local_nonpersistent_flags+=("--selector=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_create_user() -{ - last_command="pgo_create_user" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--managed") - local_nonpersistent_flags+=("--managed") - flags+=("--password=") - local_nonpersistent_flags+=("--password=") - flags+=("--password-length=") - local_nonpersistent_flags+=("--password-length=") - flags+=("--selector=") - two_word_flags+=("-s") - local_nonpersistent_flags+=("--selector=") - flags+=("--username=") - local_nonpersistent_flags+=("--username=") - flags+=("--valid-days=") - local_nonpersistent_flags+=("--valid-days=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_create() -{ - last_command="pgo_create" - - command_aliases=() - - commands=() - commands+=("cluster") - commands+=("namespace") - commands+=("pgbouncer") - commands+=("pgorole") - commands+=("pgouser") - commands+=("policy") - commands+=("schedule") - commands+=("user") - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_delete_backup() -{ - last_command="pgo_delete_backup" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_delete_cluster() -{ - last_command="pgo_delete_cluster" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--all") - local_nonpersistent_flags+=("--all") - flags+=("--delete-backups") - flags+=("-b") - local_nonpersistent_flags+=("--delete-backups") - flags+=("--delete-data") - flags+=("-d") - local_nonpersistent_flags+=("--delete-data") - flags+=("--no-prompt") - local_nonpersistent_flags+=("--no-prompt") - flags+=("--selector=") - two_word_flags+=("-s") - local_nonpersistent_flags+=("--selector=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_delete_label() -{ - last_command="pgo_delete_label" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--label=") - local_nonpersistent_flags+=("--label=") - flags+=("--selector=") - two_word_flags+=("-s") - local_nonpersistent_flags+=("--selector=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_delete_namespace() -{ - last_command="pgo_delete_namespace" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_delete_pgbouncer() -{ - last_command="pgo_delete_pgbouncer" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--no-prompt") - local_nonpersistent_flags+=("--no-prompt") - flags+=("--selector=") - two_word_flags+=("-s") - local_nonpersistent_flags+=("--selector=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_delete_pgorole() -{ - last_command="pgo_delete_pgorole" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--all") - local_nonpersistent_flags+=("--all") - flags+=("--no-prompt") - local_nonpersistent_flags+=("--no-prompt") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_delete_pgouser() -{ - last_command="pgo_delete_pgouser" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--all") - local_nonpersistent_flags+=("--all") - flags+=("--no-prompt") - local_nonpersistent_flags+=("--no-prompt") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_delete_policy() -{ - last_command="pgo_delete_policy" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--all") - local_nonpersistent_flags+=("--all") - flags+=("--no-prompt") - local_nonpersistent_flags+=("--no-prompt") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_delete_schedule() -{ - last_command="pgo_delete_schedule" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--no-prompt") - local_nonpersistent_flags+=("--no-prompt") - flags+=("--schedule-name=") - local_nonpersistent_flags+=("--schedule-name=") - flags+=("--selector=") - two_word_flags+=("-s") - local_nonpersistent_flags+=("--selector=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_delete_user() -{ - last_command="pgo_delete_user" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--all") - local_nonpersistent_flags+=("--all") - flags+=("--no-prompt") - local_nonpersistent_flags+=("--no-prompt") - flags+=("--selector=") - two_word_flags+=("-s") - local_nonpersistent_flags+=("--selector=") - flags+=("--username=") - local_nonpersistent_flags+=("--username=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_delete() -{ - last_command="pgo_delete" - - command_aliases=() - - commands=() - commands+=("backup") - commands+=("cluster") - commands+=("label") - commands+=("namespace") - commands+=("pgbouncer") - commands+=("pgorole") - commands+=("pgouser") - commands+=("policy") - commands+=("schedule") - commands+=("user") - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_df() -{ - last_command="pgo_df" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--selector=") - two_word_flags+=("-s") - local_nonpersistent_flags+=("--selector=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_failover() -{ - last_command="pgo_failover" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--autofail-replace-replica=") - local_nonpersistent_flags+=("--autofail-replace-replica=") - flags+=("--no-prompt") - local_nonpersistent_flags+=("--no-prompt") - flags+=("--query") - local_nonpersistent_flags+=("--query") - flags+=("--target=") - local_nonpersistent_flags+=("--target=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_label() -{ - last_command="pgo_label" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--dry-run") - local_nonpersistent_flags+=("--dry-run") - flags+=("--label=") - local_nonpersistent_flags+=("--label=") - flags+=("--selector=") - two_word_flags+=("-s") - local_nonpersistent_flags+=("--selector=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_ls() -{ - last_command="pgo_ls" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_reload() -{ - last_command="pgo_reload" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--no-prompt") - local_nonpersistent_flags+=("--no-prompt") - flags+=("--selector=") - two_word_flags+=("-s") - local_nonpersistent_flags+=("--selector=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_restore() -{ - last_command="pgo_restore" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--backup-opts=") - local_nonpersistent_flags+=("--backup-opts=") - flags+=("--backup-path=") - local_nonpersistent_flags+=("--backup-path=") - flags+=("--backup-pvc=") - local_nonpersistent_flags+=("--backup-pvc=") - flags+=("--backup-type=") - local_nonpersistent_flags+=("--backup-type=") - flags+=("--no-prompt") - local_nonpersistent_flags+=("--no-prompt") - flags+=("--node-label=") - local_nonpersistent_flags+=("--node-label=") - flags+=("--pgbackrest-storage-type=") - local_nonpersistent_flags+=("--pgbackrest-storage-type=") - flags+=("--pitr-target=") - local_nonpersistent_flags+=("--pitr-target=") - flags+=("--restore-to-pvc=") - local_nonpersistent_flags+=("--restore-to-pvc=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_scale() -{ - last_command="pgo_scale" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--ccp-image-tag=") - local_nonpersistent_flags+=("--ccp-image-tag=") - flags+=("--no-prompt") - local_nonpersistent_flags+=("--no-prompt") - flags+=("--node-label=") - local_nonpersistent_flags+=("--node-label=") - flags+=("--replica-count=") - local_nonpersistent_flags+=("--replica-count=") - flags+=("--service-type=") - local_nonpersistent_flags+=("--service-type=") - flags+=("--storage-config=") - local_nonpersistent_flags+=("--storage-config=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_scaledown() -{ - last_command="pgo_scaledown" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--delete-data") - flags+=("-d") - local_nonpersistent_flags+=("--delete-data") - flags+=("--no-prompt") - local_nonpersistent_flags+=("--no-prompt") - flags+=("--query") - local_nonpersistent_flags+=("--query") - flags+=("--target=") - local_nonpersistent_flags+=("--target=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_show_backup() -{ - last_command="pgo_show_backup" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--backup-type=") - local_nonpersistent_flags+=("--backup-type=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_show_cluster() -{ - last_command="pgo_show_cluster" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--all") - local_nonpersistent_flags+=("--all") - flags+=("--ccp-image-tag=") - local_nonpersistent_flags+=("--ccp-image-tag=") - flags+=("--output=") - two_word_flags+=("-o") - local_nonpersistent_flags+=("--output=") - flags+=("--selector=") - two_word_flags+=("-s") - local_nonpersistent_flags+=("--selector=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_show_config() -{ - last_command="pgo_show_config" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_show_namespace() -{ - last_command="pgo_show_namespace" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--all") - local_nonpersistent_flags+=("--all") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_show_pgorole() -{ - last_command="pgo_show_pgorole" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--all") - local_nonpersistent_flags+=("--all") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_show_pgouser() -{ - last_command="pgo_show_pgouser" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--all") - local_nonpersistent_flags+=("--all") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_show_policy() -{ - last_command="pgo_show_policy" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--all") - local_nonpersistent_flags+=("--all") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_show_pvc() -{ - last_command="pgo_show_pvc" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--all") - local_nonpersistent_flags+=("--all") - flags+=("--node-label=") - local_nonpersistent_flags+=("--node-label=") - flags+=("--pvc-root=") - local_nonpersistent_flags+=("--pvc-root=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_show_schedule() -{ - last_command="pgo_show_schedule" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--no-prompt") - local_nonpersistent_flags+=("--no-prompt") - flags+=("--schedule-name=") - local_nonpersistent_flags+=("--schedule-name=") - flags+=("--selector=") - two_word_flags+=("-s") - local_nonpersistent_flags+=("--selector=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_show_user() -{ - last_command="pgo_show_user" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--all") - local_nonpersistent_flags+=("--all") - flags+=("--expired=") - local_nonpersistent_flags+=("--expired=") - flags+=("--selector=") - two_word_flags+=("-s") - local_nonpersistent_flags+=("--selector=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_show_workflow() -{ - last_command="pgo_show_workflow" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_show() -{ - last_command="pgo_show" - - command_aliases=() - - commands=() - commands+=("backup") - commands+=("cluster") - commands+=("config") - commands+=("namespace") - commands+=("pgorole") - commands+=("pgouser") - commands+=("policy") - commands+=("pvc") - commands+=("schedule") - commands+=("user") - commands+=("workflow") - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_status() -{ - last_command="pgo_status" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--output=") - two_word_flags+=("-o") - local_nonpersistent_flags+=("--output=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_test() -{ - last_command="pgo_test" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--all") - local_nonpersistent_flags+=("--all") - flags+=("--output=") - two_word_flags+=("-o") - local_nonpersistent_flags+=("--output=") - flags+=("--selector=") - two_word_flags+=("-s") - local_nonpersistent_flags+=("--selector=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_update_cluster() -{ - last_command="pgo_update_cluster" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--all") - local_nonpersistent_flags+=("--all") - flags+=("--autofail") - local_nonpersistent_flags+=("--autofail") - flags+=("--no-prompt") - local_nonpersistent_flags+=("--no-prompt") - flags+=("--selector=") - two_word_flags+=("-s") - local_nonpersistent_flags+=("--selector=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_update_namespace() -{ - last_command="pgo_update_namespace" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_update_pgorole() -{ - last_command="pgo_update_pgorole" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--no-prompt") - local_nonpersistent_flags+=("--no-prompt") - flags+=("--permissions=") - local_nonpersistent_flags+=("--permissions=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_update_pgouser() -{ - last_command="pgo_update_pgouser" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--all-namespaces") - local_nonpersistent_flags+=("--all-namespaces") - flags+=("--no-prompt") - local_nonpersistent_flags+=("--no-prompt") - flags+=("--pgouser-namespaces=") - local_nonpersistent_flags+=("--pgouser-namespaces=") - flags+=("--pgouser-password=") - local_nonpersistent_flags+=("--pgouser-password=") - flags+=("--pgouser-roles=") - local_nonpersistent_flags+=("--pgouser-roles=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_update_user() -{ - last_command="pgo_update_user" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--all") - local_nonpersistent_flags+=("--all") - flags+=("--expire-user") - local_nonpersistent_flags+=("--expire-user") - flags+=("--expired=") - local_nonpersistent_flags+=("--expired=") - flags+=("--password=") - local_nonpersistent_flags+=("--password=") - flags+=("--password-length=") - local_nonpersistent_flags+=("--password-length=") - flags+=("--selector=") - two_word_flags+=("-s") - local_nonpersistent_flags+=("--selector=") - flags+=("--username=") - local_nonpersistent_flags+=("--username=") - flags+=("--valid-days=") - local_nonpersistent_flags+=("--valid-days=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_update() -{ - last_command="pgo_update" - - command_aliases=() - - commands=() - commands+=("cluster") - commands+=("namespace") - commands+=("pgorole") - commands+=("pgouser") - commands+=("user") - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_upgrade() -{ - last_command="pgo_upgrade" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--ccp-image-tag=") - local_nonpersistent_flags+=("--ccp-image-tag=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_version() -{ - last_command="pgo_version" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--help") - flags+=("-h") - local_nonpersistent_flags+=("--help") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_watch() -{ - last_command="pgo_watch" - - command_aliases=() - - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--pgo-event-address=") - two_word_flags+=("-a") - local_nonpersistent_flags+=("--pgo-event-address=") - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_pgo_root_command() -{ - last_command="pgo" - - command_aliases=() - - commands=() - commands+=("apply") - commands+=("backup") - commands+=("cat") - commands+=("create") - commands+=("delete") - commands+=("df") - commands+=("failover") - commands+=("label") - commands+=("load") - commands+=("ls") - commands+=("reload") - commands+=("restore") - commands+=("scale") - commands+=("scaledown") - commands+=("show") - commands+=("status") - commands+=("test") - commands+=("update") - commands+=("upgrade") - commands+=("version") - commands+=("watch") - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--apiserver-url=") - flags+=("--debug") - flags+=("--namespace=") - two_word_flags+=("-n") - flags+=("--pgo-ca-cert=") - flags+=("--pgo-client-cert=") - flags+=("--pgo-client-key=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -__start_pgo() -{ - local cur prev words cword - declare -A flaghash 2>/dev/null || : - declare -A aliashash 2>/dev/null || : - if declare -F _init_completion >/dev/null 2>&1; then - _init_completion -s || return - else - __pgo_init_completion -n "=" || return - fi - - local c=0 - local flags=() - local two_word_flags=() - local local_nonpersistent_flags=() - local flags_with_completion=() - local flags_completion=() - local commands=("pgo") - local must_have_one_flag=() - local must_have_one_noun=() - local last_command - local nouns=() - - __pgo_handle_word -} - -if [[ $(type -t compopt) = "builtin" ]]; then - complete -o default -F __start_pgo pgo -else - complete -o default -o nospace -F __start_pgo pgo -fi - -# ex: ts=4 sw=4 et filetype=sh From 31495ae4663e372fd7cc6d36bb152811ba0d6ba4 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sat, 2 Jan 2021 11:35:05 -0500 Subject: [PATCH 104/276] Add ServiceType support for pgBouncer There are cases where one may want to use a different ServiceType for pgBouncer compared to the PostgreSQL cluster. This patch adds the `serviceType` attribute to the pgBouncer Spec in the pgclusters CRD, and adds the following flags to the commands: - `pgo create cluster --pgbouncer-service-type` - `pgo create pgbouncer --service-type` - `pgo update pgbouncer --service-type` Accepted values match Kubernetes Service Types. --- cmd/pgo/cmd/cluster.go | 1 + cmd/pgo/cmd/create.go | 5 ++- cmd/pgo/cmd/pgbouncer.go | 3 ++ cmd/pgo/cmd/update.go | 1 + docs/content/custom-resources/_index.md | 1 + .../reference/pgo_create_cluster.md | 3 +- .../reference/pgo_create_pgbouncer.md | 3 +- .../reference/pgo_update_pgbouncer.md | 5 ++- .../apiserver/clusterservice/clusterimpl.go | 25 ++++++++--- .../pgbouncerservice/pgbouncerimpl.go | 24 ++++++++++ internal/operator/cluster/pgbouncer.go | 45 ++++++++++++++++--- pkg/apis/crunchydata.com/v1/cluster.go | 4 ++ pkg/apiservermsgs/clustermsgs.go | 4 ++ pkg/apiservermsgs/pgbouncermsgs.go | 9 ++++ 14 files changed, 117 insertions(+), 16 deletions(-) diff --git a/cmd/pgo/cmd/cluster.go b/cmd/pgo/cmd/cluster.go index 9d0465da22..f8db1a8409 100644 --- a/cmd/pgo/cmd/cluster.go +++ b/cmd/pgo/cmd/cluster.go @@ -280,6 +280,7 @@ func createCluster(args []string, ns string, createClusterCmd *cobra.Command) { r.ExporterMemoryLimit = ExporterMemoryLimit r.BadgerFlag = BadgerFlag r.ServiceType = v1.ServiceType(ServiceType) + r.PgBouncerServiceType = v1.ServiceType(ServiceTypePgBouncer) r.AutofailFlag = !DisableAutofailFlag r.PgbouncerFlag = PgbouncerFlag r.BackrestStorageConfig = BackrestStorageConfig diff --git a/cmd/pgo/cmd/create.go b/cmd/pgo/cmd/create.go index 19d1612f1a..36fd950a71 100644 --- a/cmd/pgo/cmd/create.go +++ b/cmd/pgo/cmd/create.go @@ -42,6 +42,7 @@ var ( UserLabels string Tablespaces []string ServiceType string + ServiceTypePgBouncer string Schedule string ScheduleOptions string ScheduleType string @@ -453,6 +454,7 @@ func init() { createClusterCmd.Flags().StringVar(&PgBouncerMemoryLimit, "pgbouncer-memory-limit", "", "Set the amount of memory to limit for "+ "pgBouncer.") createClusterCmd.Flags().Int32Var(&PgBouncerReplicas, "pgbouncer-replicas", 0, "Set the total number of pgBouncer instances to deploy. If not set, defaults to 1.") + createClusterCmd.Flags().StringVar(&ServiceTypePgBouncer, "pgbouncer-service-type", "", "The Service type to use for pgBouncer. Defaults to the Service type of the PostgreSQL cluster.") createClusterCmd.Flags().StringVar(&PgBouncerTLSSecret, "pgbouncer-tls-secret", "", "The name of the secret "+ "that contains the TLS keypair to use for enabling pgBouncer to accept TLS connections. "+ "Must also set server-tls-secret and server-ca-secret.") @@ -486,7 +488,7 @@ func init() { createClusterCmd.Flags().StringVar(&TLSSecret, "server-tls-secret", "", "The name of the secret that contains "+ "the TLS keypair to use for enabling the PostgreSQL cluster to accept TLS connections. "+ "Must be used with \"server-ca-secret\"") - createClusterCmd.Flags().StringVarP(&ServiceType, "service-type", "", "", "The Service type to use for the PostgreSQL cluster. If not set, the pgo.yaml default will be used.") + createClusterCmd.Flags().StringVar(&ServiceType, "service-type", "", "The Service type to use for the PostgreSQL cluster. If not set, the pgo.yaml default will be used.") createClusterCmd.Flags().BoolVar(&ShowSystemAccounts, "show-system-accounts", false, "Include the system accounts in the results.") createClusterCmd.Flags().StringVarP(&StorageConfig, "storage-config", "", "", "The name of a Storage config in pgo.yaml to use for the cluster storage.") createClusterCmd.Flags().BoolVarP(&SyncReplication, "sync-replication", "", false, @@ -529,6 +531,7 @@ func init() { "pgBouncer.") createPgbouncerCmd.Flags().Int32Var(&PgBouncerReplicas, "replicas", 0, "Set the total number of pgBouncer instances to deploy. If not set, defaults to 1.") createPgbouncerCmd.Flags().StringVarP(&Selector, "selector", "s", "", "The selector to use for cluster filtering.") + createPgbouncerCmd.Flags().StringVar(&ServiceType, "service-type", "", "The Service type to use for pgBouncer. Defaults to the Service type of the PostgreSQL cluster.") createPgbouncerCmd.Flags().StringVar(&PgBouncerTLSSecret, "tls-secret", "", "The name of the secret "+ "that contains the TLS keypair to use for enabling pgBouncer to accept TLS connections. "+ "The PostgreSQL cluster must have TLS enabled.") diff --git a/cmd/pgo/cmd/pgbouncer.go b/cmd/pgo/cmd/pgbouncer.go index f9096e4419..b7ec28506f 100644 --- a/cmd/pgo/cmd/pgbouncer.go +++ b/cmd/pgo/cmd/pgbouncer.go @@ -23,6 +23,7 @@ import ( "github.com/crunchydata/postgres-operator/cmd/pgo/api" "github.com/crunchydata/postgres-operator/cmd/pgo/util" msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" + v1 "k8s.io/api/core/v1" ) // showPgBouncerTextPadding contains the values for what the text padding should be @@ -67,6 +68,7 @@ func createPgbouncer(args []string, ns string) { Namespace: ns, Replicas: PgBouncerReplicas, Selector: Selector, + ServiceType: v1.ServiceType(ServiceType), TLSSecret: PgBouncerTLSSecret, } @@ -367,6 +369,7 @@ func updatePgBouncer(namespace string, clusterNames []string) { Replicas: PgBouncerReplicas, RotatePassword: RotatePassword, Selector: Selector, + ServiceType: v1.ServiceType(ServiceType), } if err := util.ValidateQuantity(request.CPURequest, "cpu"); err != nil { diff --git a/cmd/pgo/cmd/update.go b/cmd/pgo/cmd/update.go index c11e352094..e64c48ed76 100644 --- a/cmd/pgo/cmd/update.go +++ b/cmd/pgo/cmd/update.go @@ -156,6 +156,7 @@ func init() { UpdatePgBouncerCmd.Flags().Int32Var(&PgBouncerReplicas, "replicas", 0, "Set the total number of pgBouncer instances to deploy. If not set, defaults to 1.") UpdatePgBouncerCmd.Flags().BoolVar(&RotatePassword, "rotate-password", false, "Used to rotate the pgBouncer service account password. Can cause interruption of service.") UpdatePgBouncerCmd.Flags().StringVarP(&Selector, "selector", "s", "", "The selector to use for cluster filtering.") + UpdatePgBouncerCmd.Flags().StringVar(&ServiceType, "service-type", "", "The Service type to use for pgBouncer.") UpdatePgouserCmd.Flags().StringVarP(&PgouserNamespaces, "pgouser-namespaces", "", "", "The namespaces to use for updating the pgouser roles.") UpdatePgouserCmd.Flags().BoolVar(&AllNamespaces, "all-namespaces", false, "all namespaces.") UpdatePgouserCmd.Flags().StringVarP(&PgouserRoles, "pgouser-roles", "", "", "The roles to use for updating the pgouser roles.") diff --git a/docs/content/custom-resources/_index.md b/docs/content/custom-resources/_index.md index 84589f100e..3559d0eb14 100644 --- a/docs/content/custom-resources/_index.md +++ b/docs/content/custom-resources/_index.md @@ -855,6 +855,7 @@ a PostgreSQL cluster to help with failover scenarios too. | Limits | `create`, `update` | Specify the container resource limits that the pgBouncer Pods should use. Follows the [Kubernetes definitions of resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | | Replicas | `create`, `update` | The number of pgBouncer instances to deploy. Must be set to at least `1` to deploy pgBouncer. Setting to `0` removes an existing pgBouncer deployment for the PostgreSQL cluster. | | Resources | `create`, `update` | Specify the container resource requests that the pgBouncer Pods should use. Follows the [Kubernetes definitions of resource requests](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | +| ServiceType | `create`, `update` | Sets the Kubernetes [Service](https://kubernetes.io/docs/concepts/services-networking/service/) type to use for the cluster. If not set, defaults to the `ServiceType` set for the PostgreSQL cluster. | | TLSSecret | `create` | A reference to the name of a Kubernetes TLS Secret that contains a keypair that is used for the pgBouncer instance to identify itself and perform TLS communications with PostgreSQL clients. Must be used with the parent Spec `TLSSecret` and `CASecret`. | ##### Annotations Specification diff --git a/docs/content/pgo-client/reference/pgo_create_cluster.md b/docs/content/pgo-client/reference/pgo_create_cluster.md index bc5dd28f99..26ee79dcfc 100644 --- a/docs/content/pgo-client/reference/pgo_create_cluster.md +++ b/docs/content/pgo-client/reference/pgo_create_cluster.md @@ -80,6 +80,7 @@ pgo create cluster [flags] --pgbouncer-memory string Set the amount of memory to request for pgBouncer. Defaults to server value (24Mi). --pgbouncer-memory-limit string Set the amount of memory to limit for pgBouncer. --pgbouncer-replicas int32 Set the total number of pgBouncer instances to deploy. If not set, defaults to 1. + --pgbouncer-service-type string The Service type to use for pgBouncer. Defaults to the Service type of the PostgreSQL cluster. --pgbouncer-tls-secret string The name of the secret that contains the TLS keypair to use for enabling pgBouncer to accept TLS connections. Must also set server-tls-secret and server-ca-secret. --pgo-image-prefix string The PGOImagePrefix to use for cluster creation. If specified, overrides the global configuration. --pod-anti-affinity string Specifies the type of anti-affinity that should be utilized when applying default pod anti-affinity rules to PG clusters (default "preferred") @@ -135,4 +136,4 @@ pgo create cluster [flags] * [pgo create](/pgo-client/reference/pgo_create/) - Create a Postgres Operator resource -###### Auto generated by spf13/cobra on 31-Dec-2020 +###### Auto generated by spf13/cobra on 2-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_create_pgbouncer.md b/docs/content/pgo-client/reference/pgo_create_pgbouncer.md index 156820f023..beef67d591 100644 --- a/docs/content/pgo-client/reference/pgo_create_pgbouncer.md +++ b/docs/content/pgo-client/reference/pgo_create_pgbouncer.md @@ -25,6 +25,7 @@ pgo create pgbouncer [flags] --memory-limit string Set the amount of memory to limit for pgBouncer. --replicas int32 Set the total number of pgBouncer instances to deploy. If not set, defaults to 1. -s, --selector string The selector to use for cluster filtering. + --service-type string The Service type to use for pgBouncer. Defaults to the Service type of the PostgreSQL cluster. --tls-secret string The name of the secret that contains the TLS keypair to use for enabling pgBouncer to accept TLS connections. The PostgreSQL cluster must have TLS enabled. ``` @@ -45,4 +46,4 @@ pgo create pgbouncer [flags] * [pgo create](/pgo-client/reference/pgo_create/) - Create a Postgres Operator resource -###### Auto generated by spf13/cobra on 22-Nov-2020 +###### Auto generated by spf13/cobra on 1-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_update_pgbouncer.md b/docs/content/pgo-client/reference/pgo_update_pgbouncer.md index ec51137fd2..b4dd3f0247 100644 --- a/docs/content/pgo-client/reference/pgo_update_pgbouncer.md +++ b/docs/content/pgo-client/reference/pgo_update_pgbouncer.md @@ -30,12 +30,13 @@ pgo update pgbouncer [flags] --replicas int32 Set the total number of pgBouncer instances to deploy. If not set, defaults to 1. --rotate-password Used to rotate the pgBouncer service account password. Can cause interruption of service. -s, --selector string The selector to use for cluster filtering. + --service-type string The Service type to use for pgBouncer. ``` ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -49,4 +50,4 @@ pgo update pgbouncer [flags] * [pgo update](/pgo-client/reference/pgo_update/) - Update a pgouser, pgorole, or cluster -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 1-Jan-2021 diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index 784426d834..0297e548ba 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -803,11 +803,23 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. } // if the pgBouncer flag is set, validate that replicas is set to a - // nonnegative value - if request.PgbouncerFlag && request.PgBouncerReplicas < 0 { - resp.Status.Code = msgs.Error - resp.Status.Msg = fmt.Sprintf(apiserver.ErrMessageReplicas+" for pgBouncer", 1) - return resp + // nonnegative value and the service type. + if request.PgbouncerFlag { + if request.PgBouncerReplicas < 0 { + resp.Status.Code = msgs.Error + resp.Status.Msg = fmt.Sprintf(apiserver.ErrMessageReplicas+" for pgBouncer", 1) + return resp + } + + // validate the optional ServiceType parameter + switch request.PgBouncerServiceType { + default: + resp.Status.Code = msgs.Error + resp.Status.Msg = fmt.Sprintf("invalid pgBouncer service type %q", request.PgBouncerServiceType) + return resp + case v1.ServiceTypeClusterIP, v1.ServiceTypeNodePort, + v1.ServiceTypeLoadBalancer, v1.ServiceTypeExternalName, "": // no-op + } } // if a value is provided in the request for PodAntiAffinity, then ensure is valid. If @@ -1184,6 +1196,9 @@ func getClusterParams(request *msgs.CreateClusterRequest, name string, userLabel if request.PgBouncerReplicas > 0 { spec.PgBouncer.Replicas = request.PgBouncerReplicas } + + // additionally if a specific pgBouncer Service Type is set, set that here + spec.PgBouncer.ServiceType = request.PgBouncerServiceType } // similarly, if there are any overriding pgBouncer container resource request diff --git a/internal/apiserver/pgbouncerservice/pgbouncerimpl.go b/internal/apiserver/pgbouncerservice/pgbouncerimpl.go index bd4b0e8fa4..3bacd91886 100644 --- a/internal/apiserver/pgbouncerservice/pgbouncerimpl.go +++ b/internal/apiserver/pgbouncerservice/pgbouncerimpl.go @@ -109,6 +109,17 @@ func CreatePgbouncer(request *msgs.CreatePgbouncerRequest, ns, pgouser string) m cluster.Spec.PgBouncer.Replicas = request.Replicas } + // set the optional ServiceType parameter + switch request.ServiceType { + default: + resp.Status.Code = msgs.Error + resp.Status.Msg = fmt.Sprintf("invalid service type %q", request.ServiceType) + return resp + case v1.ServiceTypeClusterIP, v1.ServiceTypeNodePort, + v1.ServiceTypeLoadBalancer, v1.ServiceTypeExternalName: + cluster.Spec.PgBouncer.ServiceType = request.ServiceType + } + // if the request has overriding CPU/memory parameters, // these will take precedence over the defaults if request.CPULimit != "" { @@ -373,6 +384,19 @@ func UpdatePgBouncer(request *msgs.UpdatePgBouncerRequest, namespace, pgouser st } } + // set the optional ServiceType parameter + switch request.ServiceType { + default: + result.Error = true + result.ErrorMessage = fmt.Sprintf("invalid service type %q", request.ServiceType) + response.Results = append(response.Results, result) + continue + case v1.ServiceTypeClusterIP, v1.ServiceTypeNodePort, + v1.ServiceTypeLoadBalancer, v1.ServiceTypeExternalName: + cluster.Spec.PgBouncer.ServiceType = request.ServiceType + case "": // no-op, well, no change + } + // ensure the Resources/Limits are non-nil if cluster.Spec.PgBouncer.Resources == nil { cluster.Spec.PgBouncer.Resources = v1.ResourceList{} diff --git a/internal/operator/cluster/pgbouncer.go b/internal/operator/cluster/pgbouncer.go index 7ee7487bd4..87768ac59c 100644 --- a/internal/operator/cluster/pgbouncer.go +++ b/internal/operator/cluster/pgbouncer.go @@ -379,12 +379,20 @@ func UpdatePgbouncer(clientset kubernetes.Interface, oldCluster, newCluster *crv log.Debugf("update pgbouncer from cluster [%s] in namespace [%s]", clusterName, namespace) - // we need to detect what has changed. presently, two "groups" of things could - // have changed - // 1. The # of replicas to maintain - // 2. The pgBouncer container resources + // we need to detect what has changed. This includes: // - // As #2 is a bit more destructive, we'll do that last + // 1. The Service type for the pgBouncer Service + // 2. The # of replicas to maintain + // 3. The pgBouncer container resources + // + // As #3 is a bit more destructive, we'll do that last + + // check the pgBouncer Service + if oldCluster.Spec.PgBouncer.ServiceType != newCluster.Spec.PgBouncer.ServiceType { + if err := UpdatePgBouncerService(clientset, newCluster); err != nil { + return err + } + } // check if the replicas differ if oldCluster.Spec.PgBouncer.Replicas != newCluster.Spec.PgBouncer.Replicas { @@ -436,6 +444,25 @@ func UpdatePgBouncerAnnotations(clientset kubernetes.Interface, cluster *crv1.Pg return nil } +// UpdatePgBouncerService updates the information on the pgBouncer Service. +// Specifically, it determines if it should use the information from the parent +// PostgreSQL cluster or any specific overrides that are available in the +// pgBouncer spec. +func UpdatePgBouncerService(clientset kubernetes.Interface, cluster *crv1.Pgcluster) error { + info := serviceInfo{ + serviceName: fmt.Sprintf(pgBouncerDeploymentFormat, cluster.Name), + serviceNamespace: cluster.Namespace, + serviceType: cluster.Spec.ServiceType, + } + + // if the pgBouncer ServiceType is set, use that + if cluster.Spec.PgBouncer.ServiceType != "" { + info.serviceType = cluster.Spec.PgBouncer.ServiceType + } + + return updateService(clientset, info) +} + // checkPgBouncerInstall checks to see if pgBouncer is installed in the // PostgreSQL custer, which involves check to see if the pgBouncer role is // present in the PostgreSQL cluster @@ -643,7 +670,13 @@ func createPgBouncerService(clientset kubernetes.Interface, cluster *crv1.Pgclus ClusterName: cluster.Name, // TODO: I think "port" needs to be evaluated, but I think for now using // the standard PostgreSQL port works - Port: operator.Pgo.Cluster.Port, + Port: operator.Pgo.Cluster.Port, + ServiceType: cluster.Spec.ServiceType, + } + + // override the service type if it is set specifically for pgBouncer + if cluster.Spec.PgBouncer.ServiceType != "" { + fields.ServiceType = cluster.Spec.PgBouncer.ServiceType } if err := CreateService(clientset, &fields, cluster.Namespace); err != nil { diff --git a/pkg/apis/crunchydata.com/v1/cluster.go b/pkg/apis/crunchydata.com/v1/cluster.go index 1eade8a99f..4ad84d3b12 100644 --- a/pkg/apis/crunchydata.com/v1/cluster.go +++ b/pkg/apis/crunchydata.com/v1/cluster.go @@ -320,6 +320,10 @@ type PgBouncerSpec struct { // Limits, if specified, contains the container resource limits // for any pgBouncer Deployments that are part of a PostgreSQL cluster Limits v1.ResourceList `json:"limits"` + // ServiceType references the type of Service that should be used when + // deploying the pgBouncer instances. If unset, it defaults to the value of + // the PostgreSQL cluster. + ServiceType v1.ServiceType `json:"serviceType"` // TLSSecret contains the name of the secret to use that contains the TLS // keypair for pgBouncer // This follows the Kubernetes secret format ("kubernetes.io/tls") which has diff --git a/pkg/apiservermsgs/clustermsgs.go b/pkg/apiservermsgs/clustermsgs.go index ee2cb8aff0..ae01b0f4f4 100644 --- a/pkg/apiservermsgs/clustermsgs.go +++ b/pkg/apiservermsgs/clustermsgs.go @@ -93,6 +93,10 @@ type CreateClusterRequest struct { // PostgreSQL cluster. Only works if PgbouncerFlag is set, and if so, it must // be at least 1. If 0 is passed in, it will automatically be set to 1 PgBouncerReplicas int32 + // PgBouncerServiceType, if specified and if PgbouncerFlag is true, is the + // ServiceType to use for pgBouncer. If not set, the value is defaultd to that + // of the PostgreSQL cluster ServiceType. + PgBouncerServiceType v1.ServiceType // PgBouncerTLSSecret is the name of the Secret containing the TLS keypair // for enabling TLS with pgBouncer. This also requires for TLSSecret and // CASecret to be set diff --git a/pkg/apiservermsgs/pgbouncermsgs.go b/pkg/apiservermsgs/pgbouncermsgs.go index 49669b8fe7..e971e31424 100644 --- a/pkg/apiservermsgs/pgbouncermsgs.go +++ b/pkg/apiservermsgs/pgbouncermsgs.go @@ -1,5 +1,7 @@ package apiservermsgs +import v1 "k8s.io/api/core/v1" + /* Copyright 2018 - 2020 Crunchy Data Solutions, Inc. Licensed under the Apache License, Version 2.0 (the "License"); @@ -40,6 +42,9 @@ type CreatePgbouncerRequest struct { // automatically be set to 1 Replicas int32 Selector string + // ServiceType is the kind of Service to deploy with this instance. If unset, + // it will default to the value for the PostgreSQL cluster. + ServiceType v1.ServiceType `json:"serviceType"` // TLSSecret is the name of the secret that contains the keypair required to // deploy TLS-enabled pgBouncer TLSSecret string @@ -186,6 +191,10 @@ type UpdatePgBouncerRequest struct { // Selector is optional and contains a selector for pgBouncer deployments that // are to be updated Selector string + + // ServiceType is the kind of Service to deploy with this instance. If unset, + // it will default to the value for the PostgreSQL cluster. + ServiceType v1.ServiceType `json:"serviceType"` } // UpdatePgBouncerResponse contains the resulting output of the update request From 107b8666a067899e5a208783d7f30336e2acdcec Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sat, 2 Jan 2021 11:54:02 -0500 Subject: [PATCH 105/276] Add `--service-type` flag to `pgo update cluster` While a previous commit introduced the ability to update the Service type of a PostgreSQL cluster via a custom resource, this commit introduces the ability to do so from the pgo client. Additionally, this ensures a Service Type update to a cluster propagates to pgBouncer if the pgBouncer Service type is set to empty. --- cmd/pgo/cmd/cluster.go | 1 + cmd/pgo/cmd/update.go | 1 + .../pgo-client/reference/pgo_update_cluster.md | 3 ++- internal/apiserver/clusterservice/clusterimpl.go | 12 ++++++++++++ internal/controller/pgcluster/pgclustercontroller.go | 8 ++++++++ pkg/apiservermsgs/clustermsgs.go | 4 +++- 6 files changed, 27 insertions(+), 2 deletions(-) diff --git a/cmd/pgo/cmd/cluster.go b/cmd/pgo/cmd/cluster.go index f8db1a8409..6a7d7fd9a3 100644 --- a/cmd/pgo/cmd/cluster.go +++ b/cmd/pgo/cmd/cluster.go @@ -673,6 +673,7 @@ func updateCluster(args []string, ns string) { r.ExporterMemoryLimit = ExporterMemoryLimit r.ExporterRotatePassword = ExporterRotatePassword r.Clustername = args + r.ServiceType = v1.ServiceType(ServiceType) r.Startup = Startup r.Shutdown = Shutdown // set the container resource requests diff --git a/cmd/pgo/cmd/update.go b/cmd/pgo/cmd/update.go index e64c48ed76..0f6b06c97b 100644 --- a/cmd/pgo/cmd/update.go +++ b/cmd/pgo/cmd/update.go @@ -129,6 +129,7 @@ func init() { UpdateClusterCmd.Flags().BoolVar(&ExporterRotatePassword, "exporter-rotate-password", false, "Used to rotate the password for the metrics collection agent.") UpdateClusterCmd.Flags().BoolVarP(&EnableStandby, "enable-standby", "", false, "Enables standby mode in the cluster(s) specified.") + UpdateClusterCmd.Flags().StringVar(&ServiceType, "service-type", "", "The Service type to use for the PostgreSQL cluster. If not set, the pgo.yaml default will be used.") UpdateClusterCmd.Flags().BoolVar(&Startup, "startup", false, "Restart the database cluster if it "+ "is currently shutdown.") UpdateClusterCmd.Flags().BoolVar(&Shutdown, "shutdown", false, "Shutdown the database "+ diff --git a/docs/content/pgo-client/reference/pgo_update_cluster.md b/docs/content/pgo-client/reference/pgo_update_cluster.md index 08b1e5ae4c..70689cdfea 100644 --- a/docs/content/pgo-client/reference/pgo_update_cluster.md +++ b/docs/content/pgo-client/reference/pgo_update_cluster.md @@ -59,6 +59,7 @@ pgo update cluster [flags] --pgbackrest-memory-limit string Set the amount of memory to limit for the pgBackRest repository. --promote-standby Disables standby mode (if enabled) and promotes the cluster(s) specified. -s, --selector string The selector to use for cluster filtering. + --service-type string The Service type to use for the PostgreSQL cluster. If not set, the pgo.yaml default will be used. --shutdown Shutdown the database cluster if it is currently running. --startup Restart the database cluster if it is currently shutdown. --tablespace strings Add a PostgreSQL tablespace on the cluster, e.g. "name=ts1:storageconfig=nfsstorage". The format is a key/value map that is delimited by "=" and separated by ":". The following parameters are available: @@ -89,4 +90,4 @@ pgo update cluster [flags] * [pgo update](/pgo-client/reference/pgo_update/) - Update a pgouser, pgorole, or cluster -###### Auto generated by spf13/cobra on 31-Dec-2020 +###### Auto generated by spf13/cobra on 2-Jan-2021 diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index 0297e548ba..54a4c8b3a6 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -1900,6 +1900,18 @@ func UpdateCluster(request *msgs.UpdateClusterRequest) msgs.UpdateClusterRespons case msgs.UpdateClusterPGBadgerDoNothing: // this is never reached -- no-op } + // set the optional ServiceType parameter + switch request.ServiceType { + default: + response.Status.Code = msgs.Error + response.Status.Msg = fmt.Sprintf("invalid service type %q", request.ServiceType) + return response + case v1.ServiceTypeClusterIP, v1.ServiceTypeNodePort, + v1.ServiceTypeLoadBalancer, v1.ServiceTypeExternalName: + cluster.Spec.ServiceType = request.ServiceType + case "": // no-op, well, no change + } + // enable or disable standby mode based on UpdateClusterStandbyStatus provided in // the request switch request.Standby { diff --git a/internal/controller/pgcluster/pgclustercontroller.go b/internal/controller/pgcluster/pgclustercontroller.go index 8875ec481a..713bf6887f 100644 --- a/internal/controller/pgcluster/pgclustercontroller.go +++ b/internal/controller/pgcluster/pgclustercontroller.go @@ -493,6 +493,14 @@ func updateServices(clientset kubeapi.Interface, cluster *crv1.Pgcluster) { log.Error(err) } + // if there is a pgBouncer and the pgBouncer service type value is empty, + // update the pgBouncer Service + if cluster.Spec.PgBouncer.Enabled() && cluster.Spec.PgBouncer.ServiceType == "" { + if err := clusteroperator.UpdatePgBouncerService(clientset, cluster); err != nil { + log.Error(err) + } + } + // handle the replica instances. Ish. This is kind of "broken" due to the // fact that we have a single service for all of the replicas. so, we'll // loop through all of the replicas and try to see if any of them have diff --git a/pkg/apiservermsgs/clustermsgs.go b/pkg/apiservermsgs/clustermsgs.go index ae01b0f4f4..d6c588eb56 100644 --- a/pkg/apiservermsgs/clustermsgs.go +++ b/pkg/apiservermsgs/clustermsgs.go @@ -467,7 +467,9 @@ type UpdateClusterRequest struct { Metrics UpdateClusterMetrics // PGBadger allows for the enabling/disabling of the pgBadger sidecar. This can // cause downtime and triggers a rolling update - PGBadger UpdateClusterPGBadger + PGBadger UpdateClusterPGBadger + // ServiceType, if specified, will change the service type of a cluster. + ServiceType v1.ServiceType Standby UpdateClusterStandbyStatus Startup bool Shutdown bool From 383dfa95991553352623f14d3d0d4c9193795855 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sat, 2 Jan 2021 11:58:10 -0500 Subject: [PATCH 106/276] Happy New Year! Good riddance 2020, hello 2021. --- LICENSE.md | 2 +- bin/check-deps.sh | 2 +- bin/crunchy-postgres-exporter/common_lib.sh | 2 +- bin/crunchy-postgres-exporter/start.sh | 2 +- bin/get-deps.sh | 2 +- bin/get-pgmonitor.sh | 2 +- bin/pgo-event/pgo-event.sh | 2 +- bin/pgo-rmdata/start.sh | 2 +- bin/pgo-scheduler/start.sh | 2 +- bin/pgo-sqlrunner/start.sh | 2 +- bin/pre-pull-crunchy-containers.sh | 2 +- bin/pull-from-gcr.sh | 2 +- bin/push-ccp-to-gcr.sh | 2 +- bin/push-to-gcr.sh | 2 +- bin/uid_daemon.sh | 2 +- bin/upgrade-secret.sh | 2 +- cmd/apiserver/main.go | 2 +- cmd/pgo-rmdata/main.go | 2 +- cmd/pgo-rmdata/process.go | 2 +- cmd/pgo-rmdata/types.go | 2 +- cmd/pgo-scheduler/main.go | 2 +- cmd/pgo-scheduler/scheduler/configmapcontroller.go | 2 +- cmd/pgo-scheduler/scheduler/controllermanager.go | 2 +- cmd/pgo-scheduler/scheduler/pgbackrest.go | 2 +- cmd/pgo-scheduler/scheduler/policy.go | 2 +- cmd/pgo-scheduler/scheduler/scheduler.go | 2 +- cmd/pgo-scheduler/scheduler/tasks.go | 2 +- cmd/pgo-scheduler/scheduler/types.go | 2 +- cmd/pgo-scheduler/scheduler/validate.go | 2 +- cmd/pgo-scheduler/scheduler/validate_test.go | 2 +- cmd/pgo/api/backrest.go | 2 +- cmd/pgo/api/cat.go | 2 +- cmd/pgo/api/cluster.go | 2 +- cmd/pgo/api/common.go | 2 +- cmd/pgo/api/config.go | 2 +- cmd/pgo/api/df.go | 2 +- cmd/pgo/api/failover.go | 2 +- cmd/pgo/api/label.go | 2 +- cmd/pgo/api/namespace.go | 2 +- cmd/pgo/api/pgadmin.go | 2 +- cmd/pgo/api/pgbouncer.go | 2 +- cmd/pgo/api/pgdump.go | 2 +- cmd/pgo/api/pgorole.go | 2 +- cmd/pgo/api/pgouser.go | 2 +- cmd/pgo/api/policy.go | 2 +- cmd/pgo/api/pvc.go | 2 +- cmd/pgo/api/reload.go | 2 +- cmd/pgo/api/restart.go | 2 +- cmd/pgo/api/restore.go | 2 +- cmd/pgo/api/restoreDump.go | 2 +- cmd/pgo/api/scale.go | 2 +- cmd/pgo/api/scaledown.go | 2 +- cmd/pgo/api/schedule.go | 2 +- cmd/pgo/api/status.go | 2 +- cmd/pgo/api/test.go | 2 +- cmd/pgo/api/upgrade.go | 2 +- cmd/pgo/api/user.go | 2 +- cmd/pgo/api/version.go | 2 +- cmd/pgo/api/workflow.go | 2 +- cmd/pgo/cmd/auth.go | 2 +- cmd/pgo/cmd/backrest.go | 2 +- cmd/pgo/cmd/backup.go | 2 +- cmd/pgo/cmd/cat.go | 2 +- cmd/pgo/cmd/cluster.go | 2 +- cmd/pgo/cmd/common.go | 2 +- cmd/pgo/cmd/config.go | 2 +- cmd/pgo/cmd/create.go | 2 +- cmd/pgo/cmd/delete.go | 2 +- cmd/pgo/cmd/df.go | 2 +- cmd/pgo/cmd/failover.go | 2 +- cmd/pgo/cmd/flags.go | 2 +- cmd/pgo/cmd/label.go | 2 +- cmd/pgo/cmd/namespace.go | 2 +- cmd/pgo/cmd/pgadmin.go | 2 +- cmd/pgo/cmd/pgbouncer.go | 2 +- cmd/pgo/cmd/pgdump.go | 2 +- cmd/pgo/cmd/pgorole.go | 2 +- cmd/pgo/cmd/pgouser.go | 2 +- cmd/pgo/cmd/policy.go | 2 +- cmd/pgo/cmd/pvc.go | 2 +- cmd/pgo/cmd/reload.go | 2 +- cmd/pgo/cmd/restart.go | 2 +- cmd/pgo/cmd/restore.go | 2 +- cmd/pgo/cmd/root.go | 2 +- cmd/pgo/cmd/scale.go | 2 +- cmd/pgo/cmd/scaledown.go | 2 +- cmd/pgo/cmd/schedule.go | 2 +- cmd/pgo/cmd/show.go | 2 +- cmd/pgo/cmd/status.go | 2 +- cmd/pgo/cmd/test.go | 2 +- cmd/pgo/cmd/update.go | 2 +- cmd/pgo/cmd/upgrade.go | 2 +- cmd/pgo/cmd/user.go | 2 +- cmd/pgo/cmd/version.go | 2 +- cmd/pgo/cmd/watch.go | 2 +- cmd/pgo/cmd/workflow.go | 2 +- cmd/pgo/generatedocs.go | 2 +- cmd/pgo/main.go | 2 +- cmd/pgo/util/confirmation.go | 2 +- cmd/pgo/util/pad.go | 2 +- cmd/pgo/util/validation.go | 2 +- cmd/postgres-operator/main.go | 2 +- cmd/postgres-operator/open_telemetry.go | 2 +- deploy/add-targeted-namespace-reconcile-rbac.sh | 2 +- deploy/add-targeted-namespace.sh | 2 +- deploy/cleannamespaces.sh | 2 +- deploy/cleanup-rbac.sh | 2 +- deploy/cleanup.sh | 2 +- deploy/deploy.sh | 2 +- deploy/gen-api-keys.sh | 2 +- deploy/install-bootstrap-creds.sh | 2 +- deploy/install-rbac.sh | 2 +- deploy/remove-crd.sh | 2 +- deploy/setupnamespaces.sh | 2 +- deploy/show-crd.sh | 2 +- deploy/upgrade-creds.sh | 2 +- deploy/upgrade-pgo.sh | 2 +- examples/create-by-resource/run.sh | 2 +- examples/custom-config/create.sh | 2 +- examples/custom-config/setup.sql | 2 +- hack/boilerplate.go.txt | 2 +- hack/config_sync.sh | 2 +- hack/update-codegen.sh | 2 +- .../roles/pgo-operator/templates/add-targeted-namespace.sh.j2 | 2 +- installers/image/bin/pgo-deploy.sh | 2 +- installers/kubectl/client-setup.sh | 2 +- internal/apiserver/backrestservice/backrestimpl.go | 2 +- internal/apiserver/backrestservice/backrestservice.go | 2 +- internal/apiserver/backupoptions/backupoptionsutil.go | 2 +- internal/apiserver/backupoptions/pgbackrestoptions.go | 2 +- internal/apiserver/backupoptions/pgdumpoptions.go | 2 +- internal/apiserver/catservice/catimpl.go | 2 +- internal/apiserver/catservice/catservice.go | 2 +- internal/apiserver/clusterservice/clusterimpl.go | 2 +- internal/apiserver/clusterservice/clusterservice.go | 2 +- internal/apiserver/clusterservice/scaleimpl.go | 2 +- internal/apiserver/clusterservice/scaleservice.go | 2 +- internal/apiserver/common.go | 2 +- internal/apiserver/common_test.go | 2 +- internal/apiserver/configservice/configimpl.go | 2 +- internal/apiserver/configservice/configservice.go | 2 +- internal/apiserver/dfservice/dfimpl.go | 2 +- internal/apiserver/dfservice/dfservice.go | 2 +- internal/apiserver/failoverservice/failoverimpl.go | 2 +- internal/apiserver/failoverservice/failoverservice.go | 2 +- internal/apiserver/labelservice/labelimpl.go | 2 +- internal/apiserver/labelservice/labelservice.go | 2 +- internal/apiserver/middleware.go | 2 +- internal/apiserver/namespaceservice/namespaceimpl.go | 2 +- internal/apiserver/namespaceservice/namespaceservice.go | 2 +- internal/apiserver/perms.go | 2 +- internal/apiserver/pgadminservice/pgadminimpl.go | 2 +- internal/apiserver/pgadminservice/pgadminservice.go | 2 +- internal/apiserver/pgbouncerservice/pgbouncerimpl.go | 2 +- internal/apiserver/pgbouncerservice/pgbouncerservice.go | 2 +- internal/apiserver/pgdumpservice/pgdumpimpl.go | 2 +- internal/apiserver/pgdumpservice/pgdumpservice.go | 2 +- internal/apiserver/pgoroleservice/pgoroleimpl.go | 2 +- internal/apiserver/pgoroleservice/pgoroleservice.go | 2 +- internal/apiserver/pgouserservice/pgouserimpl.go | 2 +- internal/apiserver/pgouserservice/pgouserservice.go | 2 +- internal/apiserver/policyservice/policyimpl.go | 2 +- internal/apiserver/policyservice/policyservice.go | 2 +- internal/apiserver/pvcservice/pvcimpl.go | 2 +- internal/apiserver/pvcservice/pvcservice.go | 2 +- internal/apiserver/reloadservice/reloadimpl.go | 2 +- internal/apiserver/reloadservice/reloadservice.go | 2 +- internal/apiserver/restartservice/restartimpl.go | 2 +- internal/apiserver/restartservice/restartservice.go | 2 +- internal/apiserver/root.go | 2 +- internal/apiserver/routing/doc.go | 2 +- internal/apiserver/routing/routes.go | 2 +- internal/apiserver/scheduleservice/scheduleimpl.go | 2 +- internal/apiserver/scheduleservice/scheduleservice.go | 2 +- internal/apiserver/statusservice/statusimpl.go | 2 +- internal/apiserver/statusservice/statusservice.go | 2 +- internal/apiserver/upgradeservice/upgradeimpl.go | 2 +- internal/apiserver/upgradeservice/upgradeservice.go | 2 +- internal/apiserver/userservice/userimpl.go | 2 +- internal/apiserver/userservice/userimpl_test.go | 2 +- internal/apiserver/userservice/userservice.go | 2 +- internal/apiserver/versionservice/versionimpl.go | 2 +- internal/apiserver/versionservice/versionservice.go | 2 +- internal/apiserver/workflowservice/workflowimpl.go | 2 +- internal/apiserver/workflowservice/workflowservice.go | 2 +- internal/config/annotations.go | 2 +- internal/config/defaults.go | 2 +- internal/config/images.go | 2 +- internal/config/labels.go | 2 +- internal/config/pgoconfig.go | 2 +- internal/config/secrets.go | 2 +- internal/config/volumes.go | 2 +- internal/controller/configmap/configmapcontroller.go | 2 +- internal/controller/configmap/synchandler.go | 2 +- internal/controller/controllerutil.go | 2 +- internal/controller/job/backresthandler.go | 2 +- internal/controller/job/bootstraphandler.go | 2 +- internal/controller/job/jobcontroller.go | 2 +- internal/controller/job/jobevents.go | 2 +- internal/controller/job/jobutil.go | 2 +- internal/controller/job/pgdumphandler.go | 2 +- internal/controller/job/rmdatahandler.go | 2 +- internal/controller/manager/controllermanager.go | 2 +- internal/controller/manager/rbac.go | 2 +- internal/controller/namespace/namespacecontroller.go | 2 +- internal/controller/pgcluster/pgclustercontroller.go | 2 +- internal/controller/pgpolicy/pgpolicycontroller.go | 2 +- internal/controller/pgreplica/pgreplicacontroller.go | 2 +- internal/controller/pgtask/backresthandler.go | 2 +- internal/controller/pgtask/pgtaskcontroller.go | 2 +- internal/controller/pod/inithandler.go | 2 +- internal/controller/pod/podcontroller.go | 2 +- internal/controller/pod/podevents.go | 2 +- internal/controller/pod/promotionhandler.go | 2 +- internal/kubeapi/client_config.go | 2 +- internal/kubeapi/endpoints.go | 2 +- internal/kubeapi/errors.go | 2 +- internal/kubeapi/exec.go | 2 +- internal/kubeapi/fake/clientset.go | 2 +- internal/kubeapi/fake/fakeclients.go | 2 +- internal/kubeapi/patch.go | 2 +- internal/kubeapi/patch_test.go | 2 +- internal/kubeapi/volumes.go | 2 +- internal/kubeapi/volumes_test.go | 2 +- internal/logging/loglib.go | 2 +- internal/ns/nslogic.go | 2 +- internal/operator/backrest/backup.go | 2 +- internal/operator/backrest/repo.go | 2 +- internal/operator/backrest/restore.go | 2 +- internal/operator/backrest/stanza.go | 2 +- internal/operator/cluster/cluster.go | 2 +- internal/operator/cluster/clusterlogic.go | 2 +- internal/operator/cluster/common.go | 2 +- internal/operator/cluster/common_test.go | 2 +- internal/operator/cluster/exporter.go | 2 +- internal/operator/cluster/failover.go | 2 +- internal/operator/cluster/failoverlogic.go | 2 +- internal/operator/cluster/pgadmin.go | 2 +- internal/operator/cluster/pgbadger.go | 2 +- internal/operator/cluster/pgbouncer.go | 2 +- internal/operator/cluster/pgbouncer_test.go | 2 +- internal/operator/cluster/rmdata.go | 2 +- internal/operator/cluster/rolling.go | 2 +- internal/operator/cluster/service.go | 2 +- internal/operator/cluster/standby.go | 2 +- internal/operator/cluster/upgrade.go | 2 +- internal/operator/clusterutilities.go | 2 +- internal/operator/clusterutilities_test.go | 2 +- internal/operator/common.go | 2 +- internal/operator/common_test.go | 2 +- internal/operator/config/configutil.go | 2 +- internal/operator/config/dcs.go | 2 +- internal/operator/config/localdb.go | 2 +- internal/operator/operatorupgrade/version-check.go | 2 +- internal/operator/pgbackrest.go | 2 +- internal/operator/pgbackrest_test.go | 2 +- internal/operator/pgdump/dump.go | 2 +- internal/operator/pgdump/restore.go | 2 +- internal/operator/pvc/pvc.go | 2 +- internal/operator/storage.go | 2 +- internal/operator/storage_test.go | 2 +- internal/operator/task/applypolicies.go | 2 +- internal/operator/task/rmdata.go | 2 +- internal/operator/task/workflow.go | 2 +- internal/operator/wal.go | 2 +- internal/patroni/doc.go | 2 +- internal/patroni/patroni.go | 2 +- internal/pgadmin/backoff.go | 2 +- internal/pgadmin/backoff_test.go | 2 +- internal/pgadmin/crypto.go | 2 +- internal/pgadmin/crypto_test.go | 2 +- internal/pgadmin/doc.go | 2 +- internal/pgadmin/hash.go | 2 +- internal/pgadmin/logic.go | 2 +- internal/pgadmin/runner.go | 2 +- internal/pgadmin/server.go | 2 +- internal/postgres/doc.go | 2 +- internal/postgres/password/doc.go | 2 +- internal/postgres/password/md5.go | 2 +- internal/postgres/password/md5_test.go | 2 +- internal/postgres/password/password.go | 2 +- internal/postgres/password/password_test.go | 2 +- internal/postgres/password/scram.go | 2 +- internal/postgres/password/scram_test.go | 2 +- internal/tlsutil/primitives.go | 2 +- internal/tlsutil/primitives_test.go | 2 +- internal/util/backrest.go | 2 +- internal/util/cluster.go | 2 +- internal/util/cluster_test.go | 2 +- internal/util/exporter.go | 2 +- internal/util/exporter_test.go | 2 +- internal/util/failover.go | 2 +- internal/util/pgbouncer.go | 2 +- internal/util/policy.go | 2 +- internal/util/secrets.go | 2 +- internal/util/secrets_test.go | 2 +- internal/util/ssh.go | 2 +- internal/util/util.go | 2 +- pkg/apis/crunchydata.com/v1/cluster.go | 2 +- pkg/apis/crunchydata.com/v1/cluster_test.go | 2 +- pkg/apis/crunchydata.com/v1/common.go | 2 +- pkg/apis/crunchydata.com/v1/common_test.go | 2 +- pkg/apis/crunchydata.com/v1/doc.go | 2 +- pkg/apis/crunchydata.com/v1/errors.go | 2 +- pkg/apis/crunchydata.com/v1/policy.go | 2 +- pkg/apis/crunchydata.com/v1/register.go | 2 +- pkg/apis/crunchydata.com/v1/replica.go | 2 +- pkg/apis/crunchydata.com/v1/task.go | 2 +- pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go | 2 +- pkg/apiservermsgs/backrestmsgs.go | 2 +- pkg/apiservermsgs/catmsgs.go | 2 +- pkg/apiservermsgs/clustermsgs.go | 2 +- pkg/apiservermsgs/common.go | 2 +- pkg/apiservermsgs/configmsgs.go | 2 +- pkg/apiservermsgs/dfmsgs.go | 2 +- pkg/apiservermsgs/failovermsgs.go | 2 +- pkg/apiservermsgs/labelmsgs.go | 2 +- pkg/apiservermsgs/namespacemsgs.go | 2 +- pkg/apiservermsgs/pgadminmsgs.go | 2 +- pkg/apiservermsgs/pgbouncermsgs.go | 2 +- pkg/apiservermsgs/pgdumpmsgs.go | 2 +- pkg/apiservermsgs/pgorolemsgs.go | 2 +- pkg/apiservermsgs/pgousermsgs.go | 2 +- pkg/apiservermsgs/policymsgs.go | 2 +- pkg/apiservermsgs/pvcmsgs.go | 2 +- pkg/apiservermsgs/reloadmsgs.go | 2 +- pkg/apiservermsgs/restartmsgs.go | 2 +- pkg/apiservermsgs/schedulemsgs.go | 2 +- pkg/apiservermsgs/statusmsgs.go | 2 +- pkg/apiservermsgs/upgrademsgs.go | 2 +- pkg/apiservermsgs/usermsgs.go | 2 +- pkg/apiservermsgs/usermsgs_test.go | 2 +- pkg/apiservermsgs/versionmsgs.go | 2 +- pkg/apiservermsgs/watchmsgs.go | 2 +- pkg/apiservermsgs/workflowmsgs.go | 2 +- pkg/events/eventing.go | 2 +- pkg/events/eventtype.go | 2 +- pkg/events/pgoeventtype.go | 2 +- pkg/generated/clientset/versioned/clientset.go | 2 +- pkg/generated/clientset/versioned/doc.go | 2 +- pkg/generated/clientset/versioned/fake/clientset_generated.go | 2 +- pkg/generated/clientset/versioned/fake/doc.go | 2 +- pkg/generated/clientset/versioned/fake/register.go | 2 +- pkg/generated/clientset/versioned/scheme/doc.go | 2 +- pkg/generated/clientset/versioned/scheme/register.go | 2 +- .../typed/crunchydata.com/v1/crunchydata.com_client.go | 2 +- .../clientset/versioned/typed/crunchydata.com/v1/doc.go | 2 +- .../clientset/versioned/typed/crunchydata.com/v1/fake/doc.go | 2 +- .../crunchydata.com/v1/fake/fake_crunchydata.com_client.go | 2 +- .../versioned/typed/crunchydata.com/v1/fake/fake_pgcluster.go | 2 +- .../versioned/typed/crunchydata.com/v1/fake/fake_pgpolicy.go | 2 +- .../versioned/typed/crunchydata.com/v1/fake/fake_pgreplica.go | 2 +- .../versioned/typed/crunchydata.com/v1/fake/fake_pgtask.go | 2 +- .../versioned/typed/crunchydata.com/v1/generated_expansion.go | 2 +- .../clientset/versioned/typed/crunchydata.com/v1/pgcluster.go | 2 +- .../clientset/versioned/typed/crunchydata.com/v1/pgpolicy.go | 2 +- .../clientset/versioned/typed/crunchydata.com/v1/pgreplica.go | 2 +- .../clientset/versioned/typed/crunchydata.com/v1/pgtask.go | 2 +- .../informers/externalversions/crunchydata.com/interface.go | 2 +- .../informers/externalversions/crunchydata.com/v1/interface.go | 2 +- .../informers/externalversions/crunchydata.com/v1/pgcluster.go | 2 +- .../informers/externalversions/crunchydata.com/v1/pgpolicy.go | 2 +- .../informers/externalversions/crunchydata.com/v1/pgreplica.go | 2 +- .../informers/externalversions/crunchydata.com/v1/pgtask.go | 2 +- pkg/generated/informers/externalversions/factory.go | 2 +- pkg/generated/informers/externalversions/generic.go | 2 +- .../externalversions/internalinterfaces/factory_interfaces.go | 2 +- pkg/generated/listers/crunchydata.com/v1/expansion_generated.go | 2 +- pkg/generated/listers/crunchydata.com/v1/pgcluster.go | 2 +- pkg/generated/listers/crunchydata.com/v1/pgpolicy.go | 2 +- pkg/generated/listers/crunchydata.com/v1/pgreplica.go | 2 +- pkg/generated/listers/crunchydata.com/v1/pgtask.go | 2 +- pv/create-pv-nfs-label.sh | 2 +- pv/create-pv-nfs-legacy.sh | 2 +- pv/create-pv-nfs.sh | 2 +- pv/create-pv.sh | 2 +- pv/delete-pv.sh | 2 +- testing/pgo_cli/cluster_backup_test.go | 2 +- testing/pgo_cli/cluster_cat_test.go | 2 +- testing/pgo_cli/cluster_create_test.go | 2 +- testing/pgo_cli/cluster_delete_test.go | 2 +- testing/pgo_cli/cluster_df_test.go | 2 +- testing/pgo_cli/cluster_failover_test.go | 2 +- testing/pgo_cli/cluster_label_test.go | 2 +- testing/pgo_cli/cluster_pgbouncer_test.go | 2 +- testing/pgo_cli/cluster_policy_test.go | 2 +- testing/pgo_cli/cluster_pvc_test.go | 2 +- testing/pgo_cli/cluster_reload_test.go | 2 +- testing/pgo_cli/cluster_restart_test.go | 2 +- testing/pgo_cli/cluster_scale_test.go | 2 +- testing/pgo_cli/cluster_scaledown_test.go | 2 +- testing/pgo_cli/cluster_test_test.go | 2 +- testing/pgo_cli/cluster_user_test.go | 2 +- testing/pgo_cli/operator_namespace_test.go | 2 +- testing/pgo_cli/operator_rbac_test.go | 2 +- testing/pgo_cli/operator_test.go | 2 +- testing/pgo_cli/suite_helpers_test.go | 2 +- testing/pgo_cli/suite_pgo_cmd_test.go | 2 +- testing/pgo_cli/suite_test.go | 2 +- 399 files changed, 399 insertions(+), 399 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 90fe0562e2..f8ebe3dacd 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -176,7 +176,7 @@ END OF TERMS AND CONDITIONS - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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/bin/check-deps.sh b/bin/check-deps.sh index fd0d77ce24..ba66ec9f1d 100755 --- a/bin/check-deps.sh +++ b/bin/check-deps.sh @@ -1,6 +1,6 @@ #!/bin/bash -e -# Copyright 2020 Crunchy Data Solutions, Inc. +# Copyright 2020 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/crunchy-postgres-exporter/common_lib.sh b/bin/crunchy-postgres-exporter/common_lib.sh index 283352062b..a42a618eb1 100755 --- a/bin/crunchy-postgres-exporter/common_lib.sh +++ b/bin/crunchy-postgres-exporter/common_lib.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/crunchy-postgres-exporter/start.sh b/bin/crunchy-postgres-exporter/start.sh index a7397973a9..42ed3d6d03 100755 --- a/bin/crunchy-postgres-exporter/start.sh +++ b/bin/crunchy-postgres-exporter/start.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/get-deps.sh b/bin/get-deps.sh index edf4a08b81..0b895f329d 100755 --- a/bin/get-deps.sh +++ b/bin/get-deps.sh @@ -1,6 +1,6 @@ #!/bin/bash -e -# Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/get-pgmonitor.sh b/bin/get-pgmonitor.sh index efa62b8628..e8cf4a0e02 100755 --- a/bin/get-pgmonitor.sh +++ b/bin/get-pgmonitor.sh @@ -1,6 +1,6 @@ #!/bin/bash -e -# Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/pgo-event/pgo-event.sh b/bin/pgo-event/pgo-event.sh index cddcb2e708..1602e56193 100755 --- a/bin/pgo-event/pgo-event.sh +++ b/bin/pgo-event/pgo-event.sh @@ -1,6 +1,6 @@ #!/bin/bash -x -# Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/pgo-rmdata/start.sh b/bin/pgo-rmdata/start.sh index 95a4903289..228f891694 100755 --- a/bin/pgo-rmdata/start.sh +++ b/bin/pgo-rmdata/start.sh @@ -1,6 +1,6 @@ #!/bin/bash -x -# Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/pgo-scheduler/start.sh b/bin/pgo-scheduler/start.sh index 4a32cf8bc3..4549d82d57 100755 --- a/bin/pgo-scheduler/start.sh +++ b/bin/pgo-scheduler/start.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/pgo-sqlrunner/start.sh b/bin/pgo-sqlrunner/start.sh index 0b2eb6d417..01422d26d7 100755 --- a/bin/pgo-sqlrunner/start.sh +++ b/bin/pgo-sqlrunner/start.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/pre-pull-crunchy-containers.sh b/bin/pre-pull-crunchy-containers.sh index 5a7031f8e9..91cfcb9dc8 100755 --- a/bin/pre-pull-crunchy-containers.sh +++ b/bin/pre-pull-crunchy-containers.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/pull-from-gcr.sh b/bin/pull-from-gcr.sh index 0e57fc13db..2087443de0 100755 --- a/bin/pull-from-gcr.sh +++ b/bin/pull-from-gcr.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/push-ccp-to-gcr.sh b/bin/push-ccp-to-gcr.sh index 59e2e329e8..d476c07b0b 100755 --- a/bin/push-ccp-to-gcr.sh +++ b/bin/push-ccp-to-gcr.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/push-to-gcr.sh b/bin/push-to-gcr.sh index 4bc46b933c..cd21a868f4 100755 --- a/bin/push-to-gcr.sh +++ b/bin/push-to-gcr.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/uid_daemon.sh b/bin/uid_daemon.sh index 83d8aca5e2..bc988bae79 100755 --- a/bin/uid_daemon.sh +++ b/bin/uid_daemon.sh @@ -1,6 +1,6 @@ #!/usr/bin/bash -# Copyright 2020 Crunchy Data Solutions, Inc. +# Copyright 2020 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/upgrade-secret.sh b/bin/upgrade-secret.sh index ee93af1377..f852008890 100755 --- a/bin/upgrade-secret.sh +++ b/bin/upgrade-secret.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/cmd/apiserver/main.go b/cmd/apiserver/main.go index 8b6b1216af..562a5870a8 100644 --- a/cmd/apiserver/main.go +++ b/cmd/apiserver/main.go @@ -1,7 +1,7 @@ package main /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-rmdata/main.go b/cmd/pgo-rmdata/main.go index b4c5c2c4fc..1b3b4d82cf 100644 --- a/cmd/pgo-rmdata/main.go +++ b/cmd/pgo-rmdata/main.go @@ -1,7 +1,7 @@ package main /* -Copyright 2019 - 2020 Crunchy Data +Copyright 2019 - 2021 Crunchy Data 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 diff --git a/cmd/pgo-rmdata/process.go b/cmd/pgo-rmdata/process.go index 3449476170..f8923a8a12 100644 --- a/cmd/pgo-rmdata/process.go +++ b/cmd/pgo-rmdata/process.go @@ -1,7 +1,7 @@ package main /* -Copyright 2019 - 2020 Crunchy Data +Copyright 2019 - 2021 Crunchy Data 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 diff --git a/cmd/pgo-rmdata/types.go b/cmd/pgo-rmdata/types.go index 36a95778ff..590bf9ac13 100644 --- a/cmd/pgo-rmdata/types.go +++ b/cmd/pgo-rmdata/types.go @@ -1,7 +1,7 @@ package main /* -Copyright 2019 - 2020 Crunchy Data +Copyright 2019 - 2021 Crunchy Data 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 diff --git a/cmd/pgo-scheduler/main.go b/cmd/pgo-scheduler/main.go index 63e0c17a00..ef13e0e794 100644 --- a/cmd/pgo-scheduler/main.go +++ b/cmd/pgo-scheduler/main.go @@ -1,7 +1,7 @@ package main /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/scheduler/configmapcontroller.go b/cmd/pgo-scheduler/scheduler/configmapcontroller.go index 95d21d883c..dfb6f7aaa7 100644 --- a/cmd/pgo-scheduler/scheduler/configmapcontroller.go +++ b/cmd/pgo-scheduler/scheduler/configmapcontroller.go @@ -1,7 +1,7 @@ package scheduler /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/scheduler/controllermanager.go b/cmd/pgo-scheduler/scheduler/controllermanager.go index 055527fc64..fa9244b0d4 100644 --- a/cmd/pgo-scheduler/scheduler/controllermanager.go +++ b/cmd/pgo-scheduler/scheduler/controllermanager.go @@ -1,7 +1,7 @@ package scheduler /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/scheduler/pgbackrest.go b/cmd/pgo-scheduler/scheduler/pgbackrest.go index ef0ba90a00..62330d61e1 100644 --- a/cmd/pgo-scheduler/scheduler/pgbackrest.go +++ b/cmd/pgo-scheduler/scheduler/pgbackrest.go @@ -1,7 +1,7 @@ package scheduler /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/scheduler/policy.go b/cmd/pgo-scheduler/scheduler/policy.go index 15a93cf27f..c143df1978 100644 --- a/cmd/pgo-scheduler/scheduler/policy.go +++ b/cmd/pgo-scheduler/scheduler/policy.go @@ -1,7 +1,7 @@ package scheduler /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/scheduler/scheduler.go b/cmd/pgo-scheduler/scheduler/scheduler.go index 8d6d326936..18d49b4ebd 100644 --- a/cmd/pgo-scheduler/scheduler/scheduler.go +++ b/cmd/pgo-scheduler/scheduler/scheduler.go @@ -1,7 +1,7 @@ package scheduler /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/scheduler/tasks.go b/cmd/pgo-scheduler/scheduler/tasks.go index a2c715d3be..a8374bfbe7 100644 --- a/cmd/pgo-scheduler/scheduler/tasks.go +++ b/cmd/pgo-scheduler/scheduler/tasks.go @@ -1,7 +1,7 @@ package scheduler /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/scheduler/types.go b/cmd/pgo-scheduler/scheduler/types.go index 674ef86ad2..7c766fa539 100644 --- a/cmd/pgo-scheduler/scheduler/types.go +++ b/cmd/pgo-scheduler/scheduler/types.go @@ -1,7 +1,7 @@ package scheduler /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/scheduler/validate.go b/cmd/pgo-scheduler/scheduler/validate.go index 35bddb7e78..d483688097 100644 --- a/cmd/pgo-scheduler/scheduler/validate.go +++ b/cmd/pgo-scheduler/scheduler/validate.go @@ -1,7 +1,7 @@ package scheduler /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/scheduler/validate_test.go b/cmd/pgo-scheduler/scheduler/validate_test.go index a7401a00d7..d6cb8f2cb4 100644 --- a/cmd/pgo-scheduler/scheduler/validate_test.go +++ b/cmd/pgo-scheduler/scheduler/validate_test.go @@ -1,7 +1,7 @@ package scheduler /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/backrest.go b/cmd/pgo/api/backrest.go index ff157058fe..f71633c654 100644 --- a/cmd/pgo/api/backrest.go +++ b/cmd/pgo/api/backrest.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/cat.go b/cmd/pgo/api/cat.go index 1da601649e..8c19125034 100644 --- a/cmd/pgo/api/cat.go +++ b/cmd/pgo/api/cat.go @@ -1,7 +1,7 @@ package api /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/cluster.go b/cmd/pgo/api/cluster.go index c51425b09b..904f09bfe3 100644 --- a/cmd/pgo/api/cluster.go +++ b/cmd/pgo/api/cluster.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/common.go b/cmd/pgo/api/common.go index b541272e94..970691e516 100644 --- a/cmd/pgo/api/common.go +++ b/cmd/pgo/api/common.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/config.go b/cmd/pgo/api/config.go index c64be16cd1..375a8186c5 100644 --- a/cmd/pgo/api/config.go +++ b/cmd/pgo/api/config.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/df.go b/cmd/pgo/api/df.go index cd64ea10da..0d5215fa62 100644 --- a/cmd/pgo/api/df.go +++ b/cmd/pgo/api/df.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/failover.go b/cmd/pgo/api/failover.go index 61731da12e..15c93c32fd 100644 --- a/cmd/pgo/api/failover.go +++ b/cmd/pgo/api/failover.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/label.go b/cmd/pgo/api/label.go index b96facc729..b94f943abf 100644 --- a/cmd/pgo/api/label.go +++ b/cmd/pgo/api/label.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/namespace.go b/cmd/pgo/api/namespace.go index 1648e02384..038b705557 100644 --- a/cmd/pgo/api/namespace.go +++ b/cmd/pgo/api/namespace.go @@ -1,7 +1,7 @@ package api /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/pgadmin.go b/cmd/pgo/api/pgadmin.go index 7f4ac09d89..e22c09c1b6 100644 --- a/cmd/pgo/api/pgadmin.go +++ b/cmd/pgo/api/pgadmin.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/pgbouncer.go b/cmd/pgo/api/pgbouncer.go index 56be678166..2aca81d5d3 100644 --- a/cmd/pgo/api/pgbouncer.go +++ b/cmd/pgo/api/pgbouncer.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/pgdump.go b/cmd/pgo/api/pgdump.go index b228954c91..5bc5ab118f 100644 --- a/cmd/pgo/api/pgdump.go +++ b/cmd/pgo/api/pgdump.go @@ -1,7 +1,7 @@ package api /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/pgorole.go b/cmd/pgo/api/pgorole.go index 1157677fba..345a18a372 100644 --- a/cmd/pgo/api/pgorole.go +++ b/cmd/pgo/api/pgorole.go @@ -1,7 +1,7 @@ package api /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/pgouser.go b/cmd/pgo/api/pgouser.go index 9f8cfaf63b..ed2932297d 100644 --- a/cmd/pgo/api/pgouser.go +++ b/cmd/pgo/api/pgouser.go @@ -1,7 +1,7 @@ package api /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/policy.go b/cmd/pgo/api/policy.go index 61b4f4842c..9761b87ac3 100644 --- a/cmd/pgo/api/policy.go +++ b/cmd/pgo/api/policy.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/pvc.go b/cmd/pgo/api/pvc.go index 4c51b05423..2c021b2957 100644 --- a/cmd/pgo/api/pvc.go +++ b/cmd/pgo/api/pvc.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/reload.go b/cmd/pgo/api/reload.go index ee33d79b76..077764848e 100644 --- a/cmd/pgo/api/reload.go +++ b/cmd/pgo/api/reload.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/restart.go b/cmd/pgo/api/restart.go index f73f6cc249..7a53e87de0 100644 --- a/cmd/pgo/api/restart.go +++ b/cmd/pgo/api/restart.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/restore.go b/cmd/pgo/api/restore.go index f80efe32f5..03f04876e9 100644 --- a/cmd/pgo/api/restore.go +++ b/cmd/pgo/api/restore.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/restoreDump.go b/cmd/pgo/api/restoreDump.go index 6e1f918f7d..ad4566b4a7 100644 --- a/cmd/pgo/api/restoreDump.go +++ b/cmd/pgo/api/restoreDump.go @@ -1,7 +1,7 @@ package api /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/scale.go b/cmd/pgo/api/scale.go index 574cbc8b4c..6ae78dbf67 100644 --- a/cmd/pgo/api/scale.go +++ b/cmd/pgo/api/scale.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/scaledown.go b/cmd/pgo/api/scaledown.go index 9075c23074..4abbe638b7 100644 --- a/cmd/pgo/api/scaledown.go +++ b/cmd/pgo/api/scaledown.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/schedule.go b/cmd/pgo/api/schedule.go index 444bd04832..e2700a4b19 100644 --- a/cmd/pgo/api/schedule.go +++ b/cmd/pgo/api/schedule.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/status.go b/cmd/pgo/api/status.go index 9def02a132..4feb1432f2 100644 --- a/cmd/pgo/api/status.go +++ b/cmd/pgo/api/status.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/test.go b/cmd/pgo/api/test.go index ca70b5c132..f21d6ea919 100644 --- a/cmd/pgo/api/test.go +++ b/cmd/pgo/api/test.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/upgrade.go b/cmd/pgo/api/upgrade.go index 3613b4527a..bb780d99bf 100644 --- a/cmd/pgo/api/upgrade.go +++ b/cmd/pgo/api/upgrade.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/user.go b/cmd/pgo/api/user.go index ed1ad4fda5..48987b3561 100644 --- a/cmd/pgo/api/user.go +++ b/cmd/pgo/api/user.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/version.go b/cmd/pgo/api/version.go index a48ac5bd8e..4499c72c93 100644 --- a/cmd/pgo/api/version.go +++ b/cmd/pgo/api/version.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/workflow.go b/cmd/pgo/api/workflow.go index 46381889f4..e3cf233062 100644 --- a/cmd/pgo/api/workflow.go +++ b/cmd/pgo/api/workflow.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/auth.go b/cmd/pgo/cmd/auth.go index fda8689df6..a54dbd9db9 100644 --- a/cmd/pgo/cmd/auth.go +++ b/cmd/pgo/cmd/auth.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/backrest.go b/cmd/pgo/cmd/backrest.go index 2b149097cf..7d750e1c6b 100644 --- a/cmd/pgo/cmd/backrest.go +++ b/cmd/pgo/cmd/backrest.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/backup.go b/cmd/pgo/cmd/backup.go index 217c689f8e..72a3cc85c0 100644 --- a/cmd/pgo/cmd/backup.go +++ b/cmd/pgo/cmd/backup.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/cat.go b/cmd/pgo/cmd/cat.go index 7afe28126f..63d0d7e6f6 100644 --- a/cmd/pgo/cmd/cat.go +++ b/cmd/pgo/cmd/cat.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/cluster.go b/cmd/pgo/cmd/cluster.go index 6a7d7fd9a3..da97cef37a 100644 --- a/cmd/pgo/cmd/cluster.go +++ b/cmd/pgo/cmd/cluster.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/common.go b/cmd/pgo/cmd/common.go index 6d618e44bc..087326b57d 100644 --- a/cmd/pgo/cmd/common.go +++ b/cmd/pgo/cmd/common.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/config.go b/cmd/pgo/cmd/config.go index 0d5f4f4335..b5b34f4c29 100644 --- a/cmd/pgo/cmd/config.go +++ b/cmd/pgo/cmd/config.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/create.go b/cmd/pgo/cmd/create.go index 36fd950a71..f301111797 100644 --- a/cmd/pgo/cmd/create.go +++ b/cmd/pgo/cmd/create.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/delete.go b/cmd/pgo/cmd/delete.go index 86a6a68c7c..511204ac8e 100644 --- a/cmd/pgo/cmd/delete.go +++ b/cmd/pgo/cmd/delete.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/df.go b/cmd/pgo/cmd/df.go index bad4301ceb..c07762ef7b 100644 --- a/cmd/pgo/cmd/df.go +++ b/cmd/pgo/cmd/df.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/failover.go b/cmd/pgo/cmd/failover.go index 1459c659ae..bfbd3b0848 100644 --- a/cmd/pgo/cmd/failover.go +++ b/cmd/pgo/cmd/failover.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/flags.go b/cmd/pgo/cmd/flags.go index 28afacc651..bdaf760942 100644 --- a/cmd/pgo/cmd/flags.go +++ b/cmd/pgo/cmd/flags.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/label.go b/cmd/pgo/cmd/label.go index bd794e706b..70b18061e5 100644 --- a/cmd/pgo/cmd/label.go +++ b/cmd/pgo/cmd/label.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/namespace.go b/cmd/pgo/cmd/namespace.go index e095328360..e3a886b484 100644 --- a/cmd/pgo/cmd/namespace.go +++ b/cmd/pgo/cmd/namespace.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/pgadmin.go b/cmd/pgo/cmd/pgadmin.go index 0d034f1710..bd629e0f7e 100644 --- a/cmd/pgo/cmd/pgadmin.go +++ b/cmd/pgo/cmd/pgadmin.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/pgbouncer.go b/cmd/pgo/cmd/pgbouncer.go index b7ec28506f..ba2ec6f803 100644 --- a/cmd/pgo/cmd/pgbouncer.go +++ b/cmd/pgo/cmd/pgbouncer.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/pgdump.go b/cmd/pgo/cmd/pgdump.go index 0ca8dfd637..d10e31b43d 100644 --- a/cmd/pgo/cmd/pgdump.go +++ b/cmd/pgo/cmd/pgdump.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/pgorole.go b/cmd/pgo/cmd/pgorole.go index 180459d2ef..110eca9887 100644 --- a/cmd/pgo/cmd/pgorole.go +++ b/cmd/pgo/cmd/pgorole.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/pgouser.go b/cmd/pgo/cmd/pgouser.go index 31ea66b316..fa74050ffe 100644 --- a/cmd/pgo/cmd/pgouser.go +++ b/cmd/pgo/cmd/pgouser.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/policy.go b/cmd/pgo/cmd/policy.go index ed4aae82a8..a503910bb9 100644 --- a/cmd/pgo/cmd/policy.go +++ b/cmd/pgo/cmd/policy.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/pvc.go b/cmd/pgo/cmd/pvc.go index 3991901442..9f910f0555 100644 --- a/cmd/pgo/cmd/pvc.go +++ b/cmd/pgo/cmd/pvc.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/reload.go b/cmd/pgo/cmd/reload.go index 9d8b85b2b4..bb0098ee86 100644 --- a/cmd/pgo/cmd/reload.go +++ b/cmd/pgo/cmd/reload.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/restart.go b/cmd/pgo/cmd/restart.go index 5349a1ff7e..650bcd16fd 100644 --- a/cmd/pgo/cmd/restart.go +++ b/cmd/pgo/cmd/restart.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/restore.go b/cmd/pgo/cmd/restore.go index bb8931f5b7..309808ee8d 100644 --- a/cmd/pgo/cmd/restore.go +++ b/cmd/pgo/cmd/restore.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/root.go b/cmd/pgo/cmd/root.go index f22a92fb1a..c38ba543c6 100644 --- a/cmd/pgo/cmd/root.go +++ b/cmd/pgo/cmd/root.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/scale.go b/cmd/pgo/cmd/scale.go index dd9d8a8a95..a44fa959f2 100644 --- a/cmd/pgo/cmd/scale.go +++ b/cmd/pgo/cmd/scale.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/scaledown.go b/cmd/pgo/cmd/scaledown.go index 7b49350599..ba526c3e74 100644 --- a/cmd/pgo/cmd/scaledown.go +++ b/cmd/pgo/cmd/scaledown.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/schedule.go b/cmd/pgo/cmd/schedule.go index 86a06aa8ae..a4d66d7f69 100644 --- a/cmd/pgo/cmd/schedule.go +++ b/cmd/pgo/cmd/schedule.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/show.go b/cmd/pgo/cmd/show.go index 96a312b733..6c75afa100 100644 --- a/cmd/pgo/cmd/show.go +++ b/cmd/pgo/cmd/show.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/status.go b/cmd/pgo/cmd/status.go index 28c930f226..fd13a7f140 100644 --- a/cmd/pgo/cmd/status.go +++ b/cmd/pgo/cmd/status.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/test.go b/cmd/pgo/cmd/test.go index 89da7b7b7f..c183879247 100644 --- a/cmd/pgo/cmd/test.go +++ b/cmd/pgo/cmd/test.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/update.go b/cmd/pgo/cmd/update.go index 0f6b06c97b..89d88fe45b 100644 --- a/cmd/pgo/cmd/update.go +++ b/cmd/pgo/cmd/update.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/upgrade.go b/cmd/pgo/cmd/upgrade.go index 31122cebf7..2781210f4b 100644 --- a/cmd/pgo/cmd/upgrade.go +++ b/cmd/pgo/cmd/upgrade.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/user.go b/cmd/pgo/cmd/user.go index ae5793167e..3c61d5671e 100644 --- a/cmd/pgo/cmd/user.go +++ b/cmd/pgo/cmd/user.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/version.go b/cmd/pgo/cmd/version.go index 969f146e85..cf269d06b3 100644 --- a/cmd/pgo/cmd/version.go +++ b/cmd/pgo/cmd/version.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/watch.go b/cmd/pgo/cmd/watch.go index fce2bcbd74..79746296a7 100644 --- a/cmd/pgo/cmd/watch.go +++ b/cmd/pgo/cmd/watch.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/workflow.go b/cmd/pgo/cmd/workflow.go index 74b2741f0a..fd372fd254 100644 --- a/cmd/pgo/cmd/workflow.go +++ b/cmd/pgo/cmd/workflow.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/generatedocs.go b/cmd/pgo/generatedocs.go index a5b2d38271..8bd31cfb01 100644 --- a/cmd/pgo/generatedocs.go +++ b/cmd/pgo/generatedocs.go @@ -3,7 +3,7 @@ package main /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/main.go b/cmd/pgo/main.go index 68aa34dee6..f122259ee9 100644 --- a/cmd/pgo/main.go +++ b/cmd/pgo/main.go @@ -1,7 +1,7 @@ package main /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/util/confirmation.go b/cmd/pgo/util/confirmation.go index c227055cb1..4d15e0ae83 100644 --- a/cmd/pgo/util/confirmation.go +++ b/cmd/pgo/util/confirmation.go @@ -1,7 +1,7 @@ package util /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/util/pad.go b/cmd/pgo/util/pad.go index 276469471a..fee08615ed 100644 --- a/cmd/pgo/util/pad.go +++ b/cmd/pgo/util/pad.go @@ -1,7 +1,7 @@ package util /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/util/validation.go b/cmd/pgo/util/validation.go index 33690de426..4e011ef43f 100644 --- a/cmd/pgo/util/validation.go +++ b/cmd/pgo/util/validation.go @@ -1,7 +1,7 @@ package util /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/postgres-operator/main.go b/cmd/postgres-operator/main.go index f457b79a0d..052b11377f 100644 --- a/cmd/postgres-operator/main.go +++ b/cmd/postgres-operator/main.go @@ -1,7 +1,7 @@ package main /* -Copyright 2017 - 2020 Crunchy Data +Copyright 2017 - 2021 Crunchy Data 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 diff --git a/cmd/postgres-operator/open_telemetry.go b/cmd/postgres-operator/open_telemetry.go index e4b156bb35..3e1f51da24 100644 --- a/cmd/postgres-operator/open_telemetry.go +++ b/cmd/postgres-operator/open_telemetry.go @@ -1,7 +1,7 @@ package main /* -Copyright 2020 Crunchy Data +Copyright 2020 - 2021 Crunchy Data 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 diff --git a/deploy/add-targeted-namespace-reconcile-rbac.sh b/deploy/add-targeted-namespace-reconcile-rbac.sh index 8438c10912..533e8507f9 100755 --- a/deploy/add-targeted-namespace-reconcile-rbac.sh +++ b/deploy/add-targeted-namespace-reconcile-rbac.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2020 Crunchy Data Solutions, Inc. +# Copyright 2020 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/add-targeted-namespace.sh b/deploy/add-targeted-namespace.sh index af088314d9..61647e9feb 100755 --- a/deploy/add-targeted-namespace.sh +++ b/deploy/add-targeted-namespace.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/cleannamespaces.sh b/deploy/cleannamespaces.sh index 66cd693863..169bae6853 100755 --- a/deploy/cleannamespaces.sh +++ b/deploy/cleannamespaces.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/cleanup-rbac.sh b/deploy/cleanup-rbac.sh index 50f52bbc5f..df60c14500 100755 --- a/deploy/cleanup-rbac.sh +++ b/deploy/cleanup-rbac.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/cleanup.sh b/deploy/cleanup.sh index afe13f98c7..711e276823 100755 --- a/deploy/cleanup.sh +++ b/deploy/cleanup.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/deploy.sh b/deploy/deploy.sh index 67478acb8b..ae8b1b3eea 100755 --- a/deploy/deploy.sh +++ b/deploy/deploy.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/gen-api-keys.sh b/deploy/gen-api-keys.sh index 15b310f85f..5fd1139ced 100755 --- a/deploy/gen-api-keys.sh +++ b/deploy/gen-api-keys.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/install-bootstrap-creds.sh b/deploy/install-bootstrap-creds.sh index 1b446824d3..e25253104c 100755 --- a/deploy/install-bootstrap-creds.sh +++ b/deploy/install-bootstrap-creds.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/install-rbac.sh b/deploy/install-rbac.sh index d96532d9f1..ec7a4d7d49 100755 --- a/deploy/install-rbac.sh +++ b/deploy/install-rbac.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/remove-crd.sh b/deploy/remove-crd.sh index 764645264f..f14bbfd022 100755 --- a/deploy/remove-crd.sh +++ b/deploy/remove-crd.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/setupnamespaces.sh b/deploy/setupnamespaces.sh index 9d2188a56f..08aade3518 100755 --- a/deploy/setupnamespaces.sh +++ b/deploy/setupnamespaces.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/show-crd.sh b/deploy/show-crd.sh index 7f40285c5d..091c2b6810 100755 --- a/deploy/show-crd.sh +++ b/deploy/show-crd.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/upgrade-creds.sh b/deploy/upgrade-creds.sh index ddc0953df7..dfea9c10c1 100755 --- a/deploy/upgrade-creds.sh +++ b/deploy/upgrade-creds.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/upgrade-pgo.sh b/deploy/upgrade-pgo.sh index 72fdb420f0..87496f3c49 100755 --- a/deploy/upgrade-pgo.sh +++ b/deploy/upgrade-pgo.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2020 Crunchy Data Solutions, Inc. +# Copyright 2020 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/examples/create-by-resource/run.sh b/examples/create-by-resource/run.sh index 98bd67ec2c..afb5e896e5 100755 --- a/examples/create-by-resource/run.sh +++ b/examples/create-by-resource/run.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/examples/custom-config/create.sh b/examples/custom-config/create.sh index df6c701f2a..5900132568 100755 --- a/examples/custom-config/create.sh +++ b/examples/custom-config/create.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/examples/custom-config/setup.sql b/examples/custom-config/setup.sql index 206005eb8a..1a05bce487 100644 --- a/examples/custom-config/setup.sql +++ b/examples/custom-config/setup.sql @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + * Copyright 2017 - 2021 Crunchy Data Solutions, Inc. * 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 diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt index 8aabc9a12b..e681957476 100644 --- a/hack/boilerplate.go.txt +++ b/hack/boilerplate.go.txt @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/hack/config_sync.sh b/hack/config_sync.sh index cab45b023b..6317c556a1 100755 --- a/hack/config_sync.sh +++ b/hack/config_sync.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2020 Crunchy Data Solutions, Inc. +# Copyright 2020 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index f81fc33be6..43e607336a 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# Copyright 2020 Crunchy Data Solutions, Inc. +# Copyright 2020 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/installers/ansible/roles/pgo-operator/templates/add-targeted-namespace.sh.j2 b/installers/ansible/roles/pgo-operator/templates/add-targeted-namespace.sh.j2 index 380a8a80b7..ec5e9e82d6 100644 --- a/installers/ansible/roles/pgo-operator/templates/add-targeted-namespace.sh.j2 +++ b/installers/ansible/roles/pgo-operator/templates/add-targeted-namespace.sh.j2 @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/installers/image/bin/pgo-deploy.sh b/installers/image/bin/pgo-deploy.sh index 9a965d58be..92fc955e9a 100755 --- a/installers/image/bin/pgo-deploy.sh +++ b/installers/image/bin/pgo-deploy.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2020 Crunchy Data Solutions, Inc. +# Copyright 2020 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/installers/kubectl/client-setup.sh b/installers/kubectl/client-setup.sh index 496f25abd4..1504009506 100755 --- a/installers/kubectl/client-setup.sh +++ b/installers/kubectl/client-setup.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2020 Crunchy Data Solutions, Inc. +# Copyright 2020 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/internal/apiserver/backrestservice/backrestimpl.go b/internal/apiserver/backrestservice/backrestimpl.go index c9ae9fb943..ec1e9b2f5b 100644 --- a/internal/apiserver/backrestservice/backrestimpl.go +++ b/internal/apiserver/backrestservice/backrestimpl.go @@ -1,7 +1,7 @@ package backrestservice /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/backrestservice/backrestservice.go b/internal/apiserver/backrestservice/backrestservice.go index c9e5d4b030..d2f4810872 100644 --- a/internal/apiserver/backrestservice/backrestservice.go +++ b/internal/apiserver/backrestservice/backrestservice.go @@ -1,7 +1,7 @@ package backrestservice /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/backupoptions/backupoptionsutil.go b/internal/apiserver/backupoptions/backupoptionsutil.go index b31bd8e00f..75b922b957 100644 --- a/internal/apiserver/backupoptions/backupoptionsutil.go +++ b/internal/apiserver/backupoptions/backupoptionsutil.go @@ -1,7 +1,7 @@ package backupoptions /* -Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/backupoptions/pgbackrestoptions.go b/internal/apiserver/backupoptions/pgbackrestoptions.go index 7926f4c0da..ec18545fef 100644 --- a/internal/apiserver/backupoptions/pgbackrestoptions.go +++ b/internal/apiserver/backupoptions/pgbackrestoptions.go @@ -1,7 +1,7 @@ package backupoptions /* -Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/backupoptions/pgdumpoptions.go b/internal/apiserver/backupoptions/pgdumpoptions.go index 803a417e95..cd4c218be6 100644 --- a/internal/apiserver/backupoptions/pgdumpoptions.go +++ b/internal/apiserver/backupoptions/pgdumpoptions.go @@ -1,7 +1,7 @@ package backupoptions /* -Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/catservice/catimpl.go b/internal/apiserver/catservice/catimpl.go index e21fd76266..f1ed27bf73 100644 --- a/internal/apiserver/catservice/catimpl.go +++ b/internal/apiserver/catservice/catimpl.go @@ -1,7 +1,7 @@ package catservice /* -Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/catservice/catservice.go b/internal/apiserver/catservice/catservice.go index cf25ac5f9a..5084eb3a33 100644 --- a/internal/apiserver/catservice/catservice.go +++ b/internal/apiserver/catservice/catservice.go @@ -1,7 +1,7 @@ package catservice /* -Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index 54a4c8b3a6..22f10a7f14 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -1,7 +1,7 @@ package clusterservice /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/clusterservice/clusterservice.go b/internal/apiserver/clusterservice/clusterservice.go index 32b904eb61..c26e91cfa4 100644 --- a/internal/apiserver/clusterservice/clusterservice.go +++ b/internal/apiserver/clusterservice/clusterservice.go @@ -1,7 +1,7 @@ package clusterservice /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/clusterservice/scaleimpl.go b/internal/apiserver/clusterservice/scaleimpl.go index 36739d1e44..f8ee7c57d8 100644 --- a/internal/apiserver/clusterservice/scaleimpl.go +++ b/internal/apiserver/clusterservice/scaleimpl.go @@ -1,7 +1,7 @@ package clusterservice /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/clusterservice/scaleservice.go b/internal/apiserver/clusterservice/scaleservice.go index 810eba4086..113f730f24 100644 --- a/internal/apiserver/clusterservice/scaleservice.go +++ b/internal/apiserver/clusterservice/scaleservice.go @@ -1,7 +1,7 @@ package clusterservice /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/common.go b/internal/apiserver/common.go index 7f5592b3c4..08bc42f6ac 100644 --- a/internal/apiserver/common.go +++ b/internal/apiserver/common.go @@ -1,7 +1,7 @@ package apiserver /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/common_test.go b/internal/apiserver/common_test.go index 2475fca015..9f11dc4e49 100644 --- a/internal/apiserver/common_test.go +++ b/internal/apiserver/common_test.go @@ -1,7 +1,7 @@ package apiserver /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/configservice/configimpl.go b/internal/apiserver/configservice/configimpl.go index 76d891d5ed..dc7ec2274c 100644 --- a/internal/apiserver/configservice/configimpl.go +++ b/internal/apiserver/configservice/configimpl.go @@ -1,7 +1,7 @@ package configservice /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/configservice/configservice.go b/internal/apiserver/configservice/configservice.go index d4066b5017..fddabe30b8 100644 --- a/internal/apiserver/configservice/configservice.go +++ b/internal/apiserver/configservice/configservice.go @@ -1,7 +1,7 @@ package configservice /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/dfservice/dfimpl.go b/internal/apiserver/dfservice/dfimpl.go index 1a24d235d2..3014821621 100644 --- a/internal/apiserver/dfservice/dfimpl.go +++ b/internal/apiserver/dfservice/dfimpl.go @@ -1,7 +1,7 @@ package dfservice /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/dfservice/dfservice.go b/internal/apiserver/dfservice/dfservice.go index 89c8796b27..8f42c65eef 100644 --- a/internal/apiserver/dfservice/dfservice.go +++ b/internal/apiserver/dfservice/dfservice.go @@ -1,7 +1,7 @@ package dfservice /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/failoverservice/failoverimpl.go b/internal/apiserver/failoverservice/failoverimpl.go index 7886e3ddfa..ca25062303 100644 --- a/internal/apiserver/failoverservice/failoverimpl.go +++ b/internal/apiserver/failoverservice/failoverimpl.go @@ -1,7 +1,7 @@ package failoverservice /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/failoverservice/failoverservice.go b/internal/apiserver/failoverservice/failoverservice.go index ad42873f77..08461e9356 100644 --- a/internal/apiserver/failoverservice/failoverservice.go +++ b/internal/apiserver/failoverservice/failoverservice.go @@ -1,7 +1,7 @@ package failoverservice /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/labelservice/labelimpl.go b/internal/apiserver/labelservice/labelimpl.go index ed12991f58..2fe6883074 100644 --- a/internal/apiserver/labelservice/labelimpl.go +++ b/internal/apiserver/labelservice/labelimpl.go @@ -1,7 +1,7 @@ package labelservice /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/labelservice/labelservice.go b/internal/apiserver/labelservice/labelservice.go index e166d2d03f..de5e199ebe 100644 --- a/internal/apiserver/labelservice/labelservice.go +++ b/internal/apiserver/labelservice/labelservice.go @@ -1,7 +1,7 @@ package labelservice /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/middleware.go b/internal/apiserver/middleware.go index fc7e60e8e2..58a1fcd77d 100644 --- a/internal/apiserver/middleware.go +++ b/internal/apiserver/middleware.go @@ -1,7 +1,7 @@ package apiserver /* -Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/namespaceservice/namespaceimpl.go b/internal/apiserver/namespaceservice/namespaceimpl.go index 24b6234b48..c51d6b6f11 100644 --- a/internal/apiserver/namespaceservice/namespaceimpl.go +++ b/internal/apiserver/namespaceservice/namespaceimpl.go @@ -1,7 +1,7 @@ package namespaceservice /* -Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/namespaceservice/namespaceservice.go b/internal/apiserver/namespaceservice/namespaceservice.go index fa5918fe45..2416e27731 100644 --- a/internal/apiserver/namespaceservice/namespaceservice.go +++ b/internal/apiserver/namespaceservice/namespaceservice.go @@ -1,7 +1,7 @@ package namespaceservice /* -Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/perms.go b/internal/apiserver/perms.go index 71316b2c82..e4e95978ae 100644 --- a/internal/apiserver/perms.go +++ b/internal/apiserver/perms.go @@ -1,7 +1,7 @@ package apiserver /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgadminservice/pgadminimpl.go b/internal/apiserver/pgadminservice/pgadminimpl.go index 89bb7bcde3..a0154b37f9 100644 --- a/internal/apiserver/pgadminservice/pgadminimpl.go +++ b/internal/apiserver/pgadminservice/pgadminimpl.go @@ -1,7 +1,7 @@ package pgadminservice /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgadminservice/pgadminservice.go b/internal/apiserver/pgadminservice/pgadminservice.go index 68c1b1b3db..fc24b5b94c 100644 --- a/internal/apiserver/pgadminservice/pgadminservice.go +++ b/internal/apiserver/pgadminservice/pgadminservice.go @@ -1,7 +1,7 @@ package pgadminservice /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgbouncerservice/pgbouncerimpl.go b/internal/apiserver/pgbouncerservice/pgbouncerimpl.go index 3bacd91886..5dfc3dfc47 100644 --- a/internal/apiserver/pgbouncerservice/pgbouncerimpl.go +++ b/internal/apiserver/pgbouncerservice/pgbouncerimpl.go @@ -1,7 +1,7 @@ package pgbouncerservice /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgbouncerservice/pgbouncerservice.go b/internal/apiserver/pgbouncerservice/pgbouncerservice.go index 773514d48d..978f21bac2 100644 --- a/internal/apiserver/pgbouncerservice/pgbouncerservice.go +++ b/internal/apiserver/pgbouncerservice/pgbouncerservice.go @@ -1,7 +1,7 @@ package pgbouncerservice /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgdumpservice/pgdumpimpl.go b/internal/apiserver/pgdumpservice/pgdumpimpl.go index 3e97ede60a..52c90d9006 100644 --- a/internal/apiserver/pgdumpservice/pgdumpimpl.go +++ b/internal/apiserver/pgdumpservice/pgdumpimpl.go @@ -1,7 +1,7 @@ package pgdumpservice /* -Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgdumpservice/pgdumpservice.go b/internal/apiserver/pgdumpservice/pgdumpservice.go index 0b57ed2d75..28acf0933a 100644 --- a/internal/apiserver/pgdumpservice/pgdumpservice.go +++ b/internal/apiserver/pgdumpservice/pgdumpservice.go @@ -1,7 +1,7 @@ package pgdumpservice /* -Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgoroleservice/pgoroleimpl.go b/internal/apiserver/pgoroleservice/pgoroleimpl.go index 4192a4437b..4f10d76f3e 100644 --- a/internal/apiserver/pgoroleservice/pgoroleimpl.go +++ b/internal/apiserver/pgoroleservice/pgoroleimpl.go @@ -1,7 +1,7 @@ package pgoroleservice /* -Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgoroleservice/pgoroleservice.go b/internal/apiserver/pgoroleservice/pgoroleservice.go index 1ab26cdac6..581927de8f 100644 --- a/internal/apiserver/pgoroleservice/pgoroleservice.go +++ b/internal/apiserver/pgoroleservice/pgoroleservice.go @@ -1,7 +1,7 @@ package pgoroleservice /* -Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgouserservice/pgouserimpl.go b/internal/apiserver/pgouserservice/pgouserimpl.go index 136deb2064..415f8d1b95 100644 --- a/internal/apiserver/pgouserservice/pgouserimpl.go +++ b/internal/apiserver/pgouserservice/pgouserimpl.go @@ -1,7 +1,7 @@ package pgouserservice /* -Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgouserservice/pgouserservice.go b/internal/apiserver/pgouserservice/pgouserservice.go index f0205c5eea..5ccff33a2e 100644 --- a/internal/apiserver/pgouserservice/pgouserservice.go +++ b/internal/apiserver/pgouserservice/pgouserservice.go @@ -1,7 +1,7 @@ package pgouserservice /* -Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/policyservice/policyimpl.go b/internal/apiserver/policyservice/policyimpl.go index 21b77d4d63..59808d925a 100644 --- a/internal/apiserver/policyservice/policyimpl.go +++ b/internal/apiserver/policyservice/policyimpl.go @@ -1,7 +1,7 @@ package policyservice /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/policyservice/policyservice.go b/internal/apiserver/policyservice/policyservice.go index 00a499be64..290b8271a0 100644 --- a/internal/apiserver/policyservice/policyservice.go +++ b/internal/apiserver/policyservice/policyservice.go @@ -1,7 +1,7 @@ package policyservice /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pvcservice/pvcimpl.go b/internal/apiserver/pvcservice/pvcimpl.go index 091aa7b67d..5f22e1abba 100644 --- a/internal/apiserver/pvcservice/pvcimpl.go +++ b/internal/apiserver/pvcservice/pvcimpl.go @@ -1,7 +1,7 @@ package pvcservice /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pvcservice/pvcservice.go b/internal/apiserver/pvcservice/pvcservice.go index 332b3740b2..f3a994d078 100644 --- a/internal/apiserver/pvcservice/pvcservice.go +++ b/internal/apiserver/pvcservice/pvcservice.go @@ -1,7 +1,7 @@ package pvcservice /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/reloadservice/reloadimpl.go b/internal/apiserver/reloadservice/reloadimpl.go index 25ed88be67..cb6978e5f5 100644 --- a/internal/apiserver/reloadservice/reloadimpl.go +++ b/internal/apiserver/reloadservice/reloadimpl.go @@ -1,7 +1,7 @@ package reloadservice /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/reloadservice/reloadservice.go b/internal/apiserver/reloadservice/reloadservice.go index 149b660125..14e8463f5e 100644 --- a/internal/apiserver/reloadservice/reloadservice.go +++ b/internal/apiserver/reloadservice/reloadservice.go @@ -1,7 +1,7 @@ package reloadservice /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/restartservice/restartimpl.go b/internal/apiserver/restartservice/restartimpl.go index dc1b4f95ce..4defee23b3 100644 --- a/internal/apiserver/restartservice/restartimpl.go +++ b/internal/apiserver/restartservice/restartimpl.go @@ -1,7 +1,7 @@ package restartservice /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/restartservice/restartservice.go b/internal/apiserver/restartservice/restartservice.go index 374cb3ca93..f8d24ac4e2 100644 --- a/internal/apiserver/restartservice/restartservice.go +++ b/internal/apiserver/restartservice/restartservice.go @@ -1,7 +1,7 @@ package restartservice /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/root.go b/internal/apiserver/root.go index bf68f1b870..e8afecbd65 100644 --- a/internal/apiserver/root.go +++ b/internal/apiserver/root.go @@ -1,7 +1,7 @@ package apiserver /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/routing/doc.go b/internal/apiserver/routing/doc.go index e985fd4280..2807dfb978 100644 --- a/internal/apiserver/routing/doc.go +++ b/internal/apiserver/routing/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/routing/routes.go b/internal/apiserver/routing/routes.go index eb3f69c862..495682052a 100644 --- a/internal/apiserver/routing/routes.go +++ b/internal/apiserver/routing/routes.go @@ -1,7 +1,7 @@ package routing /* -Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/scheduleservice/scheduleimpl.go b/internal/apiserver/scheduleservice/scheduleimpl.go index 86e3bdcfad..04c027f391 100644 --- a/internal/apiserver/scheduleservice/scheduleimpl.go +++ b/internal/apiserver/scheduleservice/scheduleimpl.go @@ -1,7 +1,7 @@ package scheduleservice /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/scheduleservice/scheduleservice.go b/internal/apiserver/scheduleservice/scheduleservice.go index 3ac9205a59..4508c30e7a 100644 --- a/internal/apiserver/scheduleservice/scheduleservice.go +++ b/internal/apiserver/scheduleservice/scheduleservice.go @@ -1,7 +1,7 @@ package scheduleservice /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/statusservice/statusimpl.go b/internal/apiserver/statusservice/statusimpl.go index 7cc81278b0..4c1f72b1a1 100644 --- a/internal/apiserver/statusservice/statusimpl.go +++ b/internal/apiserver/statusservice/statusimpl.go @@ -1,7 +1,7 @@ package statusservice /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/statusservice/statusservice.go b/internal/apiserver/statusservice/statusservice.go index 3adecd9156..5618f821e4 100644 --- a/internal/apiserver/statusservice/statusservice.go +++ b/internal/apiserver/statusservice/statusservice.go @@ -1,7 +1,7 @@ package statusservice /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/upgradeservice/upgradeimpl.go b/internal/apiserver/upgradeservice/upgradeimpl.go index 66d6a806b0..ee41b1194c 100644 --- a/internal/apiserver/upgradeservice/upgradeimpl.go +++ b/internal/apiserver/upgradeservice/upgradeimpl.go @@ -1,7 +1,7 @@ package upgradeservice /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/upgradeservice/upgradeservice.go b/internal/apiserver/upgradeservice/upgradeservice.go index b058345e6a..aa4b5ebb2e 100644 --- a/internal/apiserver/upgradeservice/upgradeservice.go +++ b/internal/apiserver/upgradeservice/upgradeservice.go @@ -1,7 +1,7 @@ package upgradeservice /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/userservice/userimpl.go b/internal/apiserver/userservice/userimpl.go index 1264e9b5c1..2372edc519 100644 --- a/internal/apiserver/userservice/userimpl.go +++ b/internal/apiserver/userservice/userimpl.go @@ -1,7 +1,7 @@ package userservice /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/userservice/userimpl_test.go b/internal/apiserver/userservice/userimpl_test.go index f49d171ff4..2a458d3589 100644 --- a/internal/apiserver/userservice/userimpl_test.go +++ b/internal/apiserver/userservice/userimpl_test.go @@ -1,7 +1,7 @@ package userservice /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/userservice/userservice.go b/internal/apiserver/userservice/userservice.go index 94a9be299b..2dda7305e4 100644 --- a/internal/apiserver/userservice/userservice.go +++ b/internal/apiserver/userservice/userservice.go @@ -1,7 +1,7 @@ package userservice /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/versionservice/versionimpl.go b/internal/apiserver/versionservice/versionimpl.go index d2341d4e93..959cfae669 100644 --- a/internal/apiserver/versionservice/versionimpl.go +++ b/internal/apiserver/versionservice/versionimpl.go @@ -1,7 +1,7 @@ package versionservice /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/versionservice/versionservice.go b/internal/apiserver/versionservice/versionservice.go index bcb2adc7b0..6fd72719b5 100644 --- a/internal/apiserver/versionservice/versionservice.go +++ b/internal/apiserver/versionservice/versionservice.go @@ -1,7 +1,7 @@ package versionservice /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/workflowservice/workflowimpl.go b/internal/apiserver/workflowservice/workflowimpl.go index 3f46b2a26c..debf2a31c2 100644 --- a/internal/apiserver/workflowservice/workflowimpl.go +++ b/internal/apiserver/workflowservice/workflowimpl.go @@ -1,7 +1,7 @@ package workflowservice /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/workflowservice/workflowservice.go b/internal/apiserver/workflowservice/workflowservice.go index 35adb2b0a4..89a2e29796 100644 --- a/internal/apiserver/workflowservice/workflowservice.go +++ b/internal/apiserver/workflowservice/workflowservice.go @@ -1,7 +1,7 @@ package workflowservice /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/config/annotations.go b/internal/config/annotations.go index db8482fe0a..3f42e4e4db 100644 --- a/internal/config/annotations.go +++ b/internal/config/annotations.go @@ -1,7 +1,7 @@ package config /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/config/defaults.go b/internal/config/defaults.go index d86e404eb7..776dfd7c90 100644 --- a/internal/config/defaults.go +++ b/internal/config/defaults.go @@ -1,7 +1,7 @@ package config /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/config/images.go b/internal/config/images.go index 7ab595ed98..0deb5b4cd6 100644 --- a/internal/config/images.go +++ b/internal/config/images.go @@ -1,7 +1,7 @@ package config /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/config/labels.go b/internal/config/labels.go index 5dce3957f0..9308d17f91 100644 --- a/internal/config/labels.go +++ b/internal/config/labels.go @@ -1,7 +1,7 @@ package config /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/config/pgoconfig.go b/internal/config/pgoconfig.go index 09d78ab841..97fa7fcb6e 100644 --- a/internal/config/pgoconfig.go +++ b/internal/config/pgoconfig.go @@ -1,7 +1,7 @@ package config /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/config/secrets.go b/internal/config/secrets.go index f518c813ba..769ec70781 100644 --- a/internal/config/secrets.go +++ b/internal/config/secrets.go @@ -1,7 +1,7 @@ package config /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/config/volumes.go b/internal/config/volumes.go index 8723f9670a..f49c8d916d 100644 --- a/internal/config/volumes.go +++ b/internal/config/volumes.go @@ -1,7 +1,7 @@ package config /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/configmap/configmapcontroller.go b/internal/controller/configmap/configmapcontroller.go index a390075b67..7c9cb27d2b 100644 --- a/internal/controller/configmap/configmapcontroller.go +++ b/internal/controller/configmap/configmapcontroller.go @@ -1,7 +1,7 @@ package configmap /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/configmap/synchandler.go b/internal/controller/configmap/synchandler.go index b556f1561a..ac673b1746 100644 --- a/internal/controller/configmap/synchandler.go +++ b/internal/controller/configmap/synchandler.go @@ -1,7 +1,7 @@ package configmap /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/controllerutil.go b/internal/controller/controllerutil.go index 4b1f5da6ba..1db944cf3b 100644 --- a/internal/controller/controllerutil.go +++ b/internal/controller/controllerutil.go @@ -1,7 +1,7 @@ package controller /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/job/backresthandler.go b/internal/controller/job/backresthandler.go index 3bd90fcf83..c7f585cb5e 100644 --- a/internal/controller/job/backresthandler.go +++ b/internal/controller/job/backresthandler.go @@ -1,7 +1,7 @@ package job /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/job/bootstraphandler.go b/internal/controller/job/bootstraphandler.go index 7b64937642..6ba9cd9b19 100644 --- a/internal/controller/job/bootstraphandler.go +++ b/internal/controller/job/bootstraphandler.go @@ -1,7 +1,7 @@ package job /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/job/jobcontroller.go b/internal/controller/job/jobcontroller.go index aa11399b47..3db9406f56 100644 --- a/internal/controller/job/jobcontroller.go +++ b/internal/controller/job/jobcontroller.go @@ -1,7 +1,7 @@ package job /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/job/jobevents.go b/internal/controller/job/jobevents.go index df21ba3ef6..7d45c23006 100644 --- a/internal/controller/job/jobevents.go +++ b/internal/controller/job/jobevents.go @@ -1,7 +1,7 @@ package job /* -Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/job/jobutil.go b/internal/controller/job/jobutil.go index 78d6bb6e34..e7a3113469 100644 --- a/internal/controller/job/jobutil.go +++ b/internal/controller/job/jobutil.go @@ -1,7 +1,7 @@ package job /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/job/pgdumphandler.go b/internal/controller/job/pgdumphandler.go index 0fd25918b2..b407ec262a 100644 --- a/internal/controller/job/pgdumphandler.go +++ b/internal/controller/job/pgdumphandler.go @@ -1,7 +1,7 @@ package job /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/job/rmdatahandler.go b/internal/controller/job/rmdatahandler.go index 5fb8b0ed6c..73ce88a486 100644 --- a/internal/controller/job/rmdatahandler.go +++ b/internal/controller/job/rmdatahandler.go @@ -1,7 +1,7 @@ package job /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/manager/controllermanager.go b/internal/controller/manager/controllermanager.go index 165677b2f2..afb678d21a 100644 --- a/internal/controller/manager/controllermanager.go +++ b/internal/controller/manager/controllermanager.go @@ -1,7 +1,7 @@ package manager /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/manager/rbac.go b/internal/controller/manager/rbac.go index 8cad4ff247..4068d67b86 100644 --- a/internal/controller/manager/rbac.go +++ b/internal/controller/manager/rbac.go @@ -1,7 +1,7 @@ package manager /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/namespace/namespacecontroller.go b/internal/controller/namespace/namespacecontroller.go index 609a0715d0..0c651cfbdf 100644 --- a/internal/controller/namespace/namespacecontroller.go +++ b/internal/controller/namespace/namespacecontroller.go @@ -1,7 +1,7 @@ package namespace /* -Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/pgcluster/pgclustercontroller.go b/internal/controller/pgcluster/pgclustercontroller.go index 713bf6887f..bd7556ec3a 100644 --- a/internal/controller/pgcluster/pgclustercontroller.go +++ b/internal/controller/pgcluster/pgclustercontroller.go @@ -1,7 +1,7 @@ package pgcluster /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/pgpolicy/pgpolicycontroller.go b/internal/controller/pgpolicy/pgpolicycontroller.go index 27d640475b..5a1f6a2fc5 100644 --- a/internal/controller/pgpolicy/pgpolicycontroller.go +++ b/internal/controller/pgpolicy/pgpolicycontroller.go @@ -1,7 +1,7 @@ package pgpolicy /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/pgreplica/pgreplicacontroller.go b/internal/controller/pgreplica/pgreplicacontroller.go index c37ee16b58..5d0d8c01a8 100644 --- a/internal/controller/pgreplica/pgreplicacontroller.go +++ b/internal/controller/pgreplica/pgreplicacontroller.go @@ -1,7 +1,7 @@ package pgreplica /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/pgtask/backresthandler.go b/internal/controller/pgtask/backresthandler.go index e8f0534e6b..f1aff229df 100644 --- a/internal/controller/pgtask/backresthandler.go +++ b/internal/controller/pgtask/backresthandler.go @@ -1,7 +1,7 @@ package pgtask /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/pgtask/pgtaskcontroller.go b/internal/controller/pgtask/pgtaskcontroller.go index 4e3f041a99..d26d7231d4 100644 --- a/internal/controller/pgtask/pgtaskcontroller.go +++ b/internal/controller/pgtask/pgtaskcontroller.go @@ -1,7 +1,7 @@ package pgtask /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/pod/inithandler.go b/internal/controller/pod/inithandler.go index 7081c1dfbf..8379d4859f 100644 --- a/internal/controller/pod/inithandler.go +++ b/internal/controller/pod/inithandler.go @@ -1,7 +1,7 @@ package pod /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/pod/podcontroller.go b/internal/controller/pod/podcontroller.go index 95d81bea32..cbfe3ba2db 100644 --- a/internal/controller/pod/podcontroller.go +++ b/internal/controller/pod/podcontroller.go @@ -1,7 +1,7 @@ package pod /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/pod/podevents.go b/internal/controller/pod/podevents.go index b3086355ca..d019c2b4b4 100644 --- a/internal/controller/pod/podevents.go +++ b/internal/controller/pod/podevents.go @@ -1,7 +1,7 @@ package pod /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/pod/promotionhandler.go b/internal/controller/pod/promotionhandler.go index a1eb83530a..e1a35e25c8 100644 --- a/internal/controller/pod/promotionhandler.go +++ b/internal/controller/pod/promotionhandler.go @@ -1,7 +1,7 @@ package pod /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/client_config.go b/internal/kubeapi/client_config.go index b3eeed0c2a..c9e0b5ea5c 100644 --- a/internal/kubeapi/client_config.go +++ b/internal/kubeapi/client_config.go @@ -1,7 +1,7 @@ package kubeapi /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/endpoints.go b/internal/kubeapi/endpoints.go index 232469fe10..e871dbf5d6 100644 --- a/internal/kubeapi/endpoints.go +++ b/internal/kubeapi/endpoints.go @@ -1,7 +1,7 @@ package kubeapi /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/errors.go b/internal/kubeapi/errors.go index 829ca9f097..ab5e9c07aa 100644 --- a/internal/kubeapi/errors.go +++ b/internal/kubeapi/errors.go @@ -1,7 +1,7 @@ package kubeapi /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/exec.go b/internal/kubeapi/exec.go index b2e994d84d..c154ba1fa3 100644 --- a/internal/kubeapi/exec.go +++ b/internal/kubeapi/exec.go @@ -1,7 +1,7 @@ package kubeapi /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/fake/clientset.go b/internal/kubeapi/fake/clientset.go index 7fbd74b802..2ac060e1da 100644 --- a/internal/kubeapi/fake/clientset.go +++ b/internal/kubeapi/fake/clientset.go @@ -1,7 +1,7 @@ package fake /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/fake/fakeclients.go b/internal/kubeapi/fake/fakeclients.go index 8c7395549d..cdc96c1ec6 100644 --- a/internal/kubeapi/fake/fakeclients.go +++ b/internal/kubeapi/fake/fakeclients.go @@ -1,7 +1,7 @@ package fake /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/patch.go b/internal/kubeapi/patch.go index fcaf83a432..57f11e6867 100644 --- a/internal/kubeapi/patch.go +++ b/internal/kubeapi/patch.go @@ -1,7 +1,7 @@ package kubeapi /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/patch_test.go b/internal/kubeapi/patch_test.go index fa270e340c..706ee4768d 100644 --- a/internal/kubeapi/patch_test.go +++ b/internal/kubeapi/patch_test.go @@ -1,7 +1,7 @@ package kubeapi /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/volumes.go b/internal/kubeapi/volumes.go index 05412672ac..795b0a9151 100644 --- a/internal/kubeapi/volumes.go +++ b/internal/kubeapi/volumes.go @@ -1,7 +1,7 @@ package kubeapi /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/volumes_test.go b/internal/kubeapi/volumes_test.go index c2933d9e87..fead7d17be 100644 --- a/internal/kubeapi/volumes_test.go +++ b/internal/kubeapi/volumes_test.go @@ -1,7 +1,7 @@ package kubeapi /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/logging/loglib.go b/internal/logging/loglib.go index e346317f6e..d171ba29fd 100644 --- a/internal/logging/loglib.go +++ b/internal/logging/loglib.go @@ -2,7 +2,7 @@ package logging /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/ns/nslogic.go b/internal/ns/nslogic.go index 34df014bed..c9c53dd480 100644 --- a/internal/ns/nslogic.go +++ b/internal/ns/nslogic.go @@ -1,7 +1,7 @@ package ns /* -Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/backrest/backup.go b/internal/operator/backrest/backup.go index 8cd150cdf3..efdd97f7e9 100644 --- a/internal/operator/backrest/backup.go +++ b/internal/operator/backrest/backup.go @@ -1,7 +1,7 @@ package backrest /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/backrest/repo.go b/internal/operator/backrest/repo.go index 28debffc17..86304055d1 100644 --- a/internal/operator/backrest/repo.go +++ b/internal/operator/backrest/repo.go @@ -1,7 +1,7 @@ package backrest /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/backrest/restore.go b/internal/operator/backrest/restore.go index a8395b2484..4ff7797cda 100644 --- a/internal/operator/backrest/restore.go +++ b/internal/operator/backrest/restore.go @@ -1,7 +1,7 @@ package backrest /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/backrest/stanza.go b/internal/operator/backrest/stanza.go index ba97c225c1..e9f1fc6c50 100644 --- a/internal/operator/backrest/stanza.go +++ b/internal/operator/backrest/stanza.go @@ -1,7 +1,7 @@ package backrest /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/cluster.go b/internal/operator/cluster/cluster.go index 9319a10c00..05455daaf7 100644 --- a/internal/operator/cluster/cluster.go +++ b/internal/operator/cluster/cluster.go @@ -4,7 +4,7 @@ package cluster /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/clusterlogic.go b/internal/operator/cluster/clusterlogic.go index c7f1d4cc00..528ff775dc 100644 --- a/internal/operator/cluster/clusterlogic.go +++ b/internal/operator/cluster/clusterlogic.go @@ -4,7 +4,7 @@ package cluster /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/common.go b/internal/operator/cluster/common.go index bbd497e582..1974462b92 100644 --- a/internal/operator/cluster/common.go +++ b/internal/operator/cluster/common.go @@ -1,7 +1,7 @@ package cluster /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/common_test.go b/internal/operator/cluster/common_test.go index 8b83becb80..b2666b4d91 100644 --- a/internal/operator/cluster/common_test.go +++ b/internal/operator/cluster/common_test.go @@ -1,7 +1,7 @@ package cluster /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/exporter.go b/internal/operator/cluster/exporter.go index c57d953f38..929f06b85f 100644 --- a/internal/operator/cluster/exporter.go +++ b/internal/operator/cluster/exporter.go @@ -1,7 +1,7 @@ package cluster /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/failover.go b/internal/operator/cluster/failover.go index 5c2b43e173..d1ff4fb033 100644 --- a/internal/operator/cluster/failover.go +++ b/internal/operator/cluster/failover.go @@ -4,7 +4,7 @@ package cluster /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/failoverlogic.go b/internal/operator/cluster/failoverlogic.go index f1ec1183d6..7391de79e4 100644 --- a/internal/operator/cluster/failoverlogic.go +++ b/internal/operator/cluster/failoverlogic.go @@ -4,7 +4,7 @@ package cluster /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/pgadmin.go b/internal/operator/cluster/pgadmin.go index 9ed1bdbea5..9f058a89b7 100644 --- a/internal/operator/cluster/pgadmin.go +++ b/internal/operator/cluster/pgadmin.go @@ -1,7 +1,7 @@ package cluster /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/pgbadger.go b/internal/operator/cluster/pgbadger.go index ed1b0fdfc2..b24c40ebf5 100644 --- a/internal/operator/cluster/pgbadger.go +++ b/internal/operator/cluster/pgbadger.go @@ -1,7 +1,7 @@ package cluster /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/pgbouncer.go b/internal/operator/cluster/pgbouncer.go index 87768ac59c..35b788e9d8 100644 --- a/internal/operator/cluster/pgbouncer.go +++ b/internal/operator/cluster/pgbouncer.go @@ -1,7 +1,7 @@ package cluster /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/pgbouncer_test.go b/internal/operator/cluster/pgbouncer_test.go index 0784afa58c..2c07739e43 100644 --- a/internal/operator/cluster/pgbouncer_test.go +++ b/internal/operator/cluster/pgbouncer_test.go @@ -1,7 +1,7 @@ package cluster /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/rmdata.go b/internal/operator/cluster/rmdata.go index 27c224eaec..6aa4e986a0 100644 --- a/internal/operator/cluster/rmdata.go +++ b/internal/operator/cluster/rmdata.go @@ -4,7 +4,7 @@ package cluster /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/rolling.go b/internal/operator/cluster/rolling.go index 2860db5fbd..39d50dff10 100644 --- a/internal/operator/cluster/rolling.go +++ b/internal/operator/cluster/rolling.go @@ -1,7 +1,7 @@ package cluster /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/service.go b/internal/operator/cluster/service.go index 73edd7e35e..c651551d3d 100644 --- a/internal/operator/cluster/service.go +++ b/internal/operator/cluster/service.go @@ -4,7 +4,7 @@ package cluster /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/standby.go b/internal/operator/cluster/standby.go index 0ec759e025..696ac1ad18 100644 --- a/internal/operator/cluster/standby.go +++ b/internal/operator/cluster/standby.go @@ -1,7 +1,7 @@ package cluster /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/upgrade.go b/internal/operator/cluster/upgrade.go index fb5c344cdb..c55d405a24 100644 --- a/internal/operator/cluster/upgrade.go +++ b/internal/operator/cluster/upgrade.go @@ -1,7 +1,7 @@ package cluster /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/clusterutilities.go b/internal/operator/clusterutilities.go index fe1cb37b26..94744111ca 100644 --- a/internal/operator/clusterutilities.go +++ b/internal/operator/clusterutilities.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/clusterutilities_test.go b/internal/operator/clusterutilities_test.go index 52e31aa66c..80824ddd6e 100644 --- a/internal/operator/clusterutilities_test.go +++ b/internal/operator/clusterutilities_test.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/common.go b/internal/operator/common.go index 382fbb1498..20734af392 100644 --- a/internal/operator/common.go +++ b/internal/operator/common.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/common_test.go b/internal/operator/common_test.go index 53035c9933..88bb7f633b 100644 --- a/internal/operator/common_test.go +++ b/internal/operator/common_test.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/config/configutil.go b/internal/operator/config/configutil.go index 9c11483dac..3205d35284 100644 --- a/internal/operator/config/configutil.go +++ b/internal/operator/config/configutil.go @@ -1,7 +1,7 @@ package config /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/config/dcs.go b/internal/operator/config/dcs.go index fe405d05c1..16238bfdf4 100644 --- a/internal/operator/config/dcs.go +++ b/internal/operator/config/dcs.go @@ -1,7 +1,7 @@ package config /* - Copyright 2020 Crunchy Data Solutions, Ind. + Copyright 2020 - 2021 Crunchy Data Solutions, Ind. 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 diff --git a/internal/operator/config/localdb.go b/internal/operator/config/localdb.go index 2e4a630563..76d641d38e 100644 --- a/internal/operator/config/localdb.go +++ b/internal/operator/config/localdb.go @@ -1,7 +1,7 @@ package config /* - Copyright 2020 Crunchy Data Solutions, Inl. + Copyright 2020 - 2021 Crunchy Data Solutions, Inl. 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 diff --git a/internal/operator/operatorupgrade/version-check.go b/internal/operator/operatorupgrade/version-check.go index 8544c77b77..a63773e9f2 100644 --- a/internal/operator/operatorupgrade/version-check.go +++ b/internal/operator/operatorupgrade/version-check.go @@ -1,7 +1,7 @@ package operatorupgrade /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/pgbackrest.go b/internal/operator/pgbackrest.go index 8e369e764c..19c255da2c 100644 --- a/internal/operator/pgbackrest.go +++ b/internal/operator/pgbackrest.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/pgbackrest_test.go b/internal/operator/pgbackrest_test.go index 046d2be770..38d09a6be6 100644 --- a/internal/operator/pgbackrest_test.go +++ b/internal/operator/pgbackrest_test.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/pgdump/dump.go b/internal/operator/pgdump/dump.go index 1df0efbc79..73bf7cc0a1 100644 --- a/internal/operator/pgdump/dump.go +++ b/internal/operator/pgdump/dump.go @@ -1,7 +1,7 @@ package pgdump /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/pgdump/restore.go b/internal/operator/pgdump/restore.go index 38b118c35b..51f36d0f0e 100644 --- a/internal/operator/pgdump/restore.go +++ b/internal/operator/pgdump/restore.go @@ -1,7 +1,7 @@ package pgdump /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/pvc/pvc.go b/internal/operator/pvc/pvc.go index 21d2fc2808..5e96d67c8e 100644 --- a/internal/operator/pvc/pvc.go +++ b/internal/operator/pvc/pvc.go @@ -1,7 +1,7 @@ package pvc /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/storage.go b/internal/operator/storage.go index 83b3918ae7..b6c06b1abd 100644 --- a/internal/operator/storage.go +++ b/internal/operator/storage.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/storage_test.go b/internal/operator/storage_test.go index 44a235fff0..46b9161dbe 100644 --- a/internal/operator/storage_test.go +++ b/internal/operator/storage_test.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/task/applypolicies.go b/internal/operator/task/applypolicies.go index 11b568f0c4..823800451e 100644 --- a/internal/operator/task/applypolicies.go +++ b/internal/operator/task/applypolicies.go @@ -1,7 +1,7 @@ package task /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/task/rmdata.go b/internal/operator/task/rmdata.go index eb9c9c2fe8..d65141b207 100644 --- a/internal/operator/task/rmdata.go +++ b/internal/operator/task/rmdata.go @@ -1,7 +1,7 @@ package task /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/task/workflow.go b/internal/operator/task/workflow.go index 531e3e5eac..a890859293 100644 --- a/internal/operator/task/workflow.go +++ b/internal/operator/task/workflow.go @@ -1,7 +1,7 @@ package task /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/wal.go b/internal/operator/wal.go index 1b679755fb..b9a14c3219 100644 --- a/internal/operator/wal.go +++ b/internal/operator/wal.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/patroni/doc.go b/internal/patroni/doc.go index 63a42c84d3..6311d1e653 100644 --- a/internal/patroni/doc.go +++ b/internal/patroni/doc.go @@ -4,7 +4,7 @@ package patroni /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/patroni/patroni.go b/internal/patroni/patroni.go index 111515c02a..3b20703c0e 100644 --- a/internal/patroni/patroni.go +++ b/internal/patroni/patroni.go @@ -1,7 +1,7 @@ package patroni /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/pgadmin/backoff.go b/internal/pgadmin/backoff.go index 40cacaec13..ee62223e82 100644 --- a/internal/pgadmin/backoff.go +++ b/internal/pgadmin/backoff.go @@ -1,7 +1,7 @@ package pgadmin /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/pgadmin/backoff_test.go b/internal/pgadmin/backoff_test.go index 09c0445526..ec4195df4c 100644 --- a/internal/pgadmin/backoff_test.go +++ b/internal/pgadmin/backoff_test.go @@ -1,7 +1,7 @@ package pgadmin /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/pgadmin/crypto.go b/internal/pgadmin/crypto.go index 55ebc8b771..e2db5beb5d 100644 --- a/internal/pgadmin/crypto.go +++ b/internal/pgadmin/crypto.go @@ -1,7 +1,7 @@ package pgadmin /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/pgadmin/crypto_test.go b/internal/pgadmin/crypto_test.go index 221b23fb80..aeb18a1fcb 100644 --- a/internal/pgadmin/crypto_test.go +++ b/internal/pgadmin/crypto_test.go @@ -1,7 +1,7 @@ package pgadmin /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/pgadmin/doc.go b/internal/pgadmin/doc.go index 97900b0227..58bf983ab5 100644 --- a/internal/pgadmin/doc.go +++ b/internal/pgadmin/doc.go @@ -4,7 +4,7 @@ database which powers pgadmin */ package pgadmin /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/pgadmin/hash.go b/internal/pgadmin/hash.go index 728faed22b..beaab646c9 100644 --- a/internal/pgadmin/hash.go +++ b/internal/pgadmin/hash.go @@ -1,7 +1,7 @@ package pgadmin /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/pgadmin/logic.go b/internal/pgadmin/logic.go index 68426ae91b..9bd6cda94a 100644 --- a/internal/pgadmin/logic.go +++ b/internal/pgadmin/logic.go @@ -1,7 +1,7 @@ package pgadmin /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/pgadmin/runner.go b/internal/pgadmin/runner.go index 7ce80a484c..ab052b431c 100644 --- a/internal/pgadmin/runner.go +++ b/internal/pgadmin/runner.go @@ -1,7 +1,7 @@ package pgadmin /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/pgadmin/server.go b/internal/pgadmin/server.go index 26568e8806..b5ab3b6ef5 100644 --- a/internal/pgadmin/server.go +++ b/internal/pgadmin/server.go @@ -1,7 +1,7 @@ package pgadmin /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/postgres/doc.go b/internal/postgres/doc.go index 974cb7c8df..2a2155a2cd 100644 --- a/internal/postgres/doc.go +++ b/internal/postgres/doc.go @@ -5,7 +5,7 @@ package postgres /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/postgres/password/doc.go b/internal/postgres/password/doc.go index 6ea6563873..b0e22372f6 100644 --- a/internal/postgres/password/doc.go +++ b/internal/postgres/password/doc.go @@ -4,7 +4,7 @@ package password /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/postgres/password/md5.go b/internal/postgres/password/md5.go index 56f9504608..1697e04cb9 100644 --- a/internal/postgres/password/md5.go +++ b/internal/postgres/password/md5.go @@ -1,7 +1,7 @@ package password /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/postgres/password/md5_test.go b/internal/postgres/password/md5_test.go index 41c0711b04..7adabe8831 100644 --- a/internal/postgres/password/md5_test.go +++ b/internal/postgres/password/md5_test.go @@ -1,7 +1,7 @@ package password /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/postgres/password/password.go b/internal/postgres/password/password.go index f63fb31492..923c854f00 100644 --- a/internal/postgres/password/password.go +++ b/internal/postgres/password/password.go @@ -1,7 +1,7 @@ package password /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/postgres/password/password_test.go b/internal/postgres/password/password_test.go index d315c966bc..40962b9a75 100644 --- a/internal/postgres/password/password_test.go +++ b/internal/postgres/password/password_test.go @@ -1,7 +1,7 @@ package password /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/postgres/password/scram.go b/internal/postgres/password/scram.go index 794575e4dd..1411af9636 100644 --- a/internal/postgres/password/scram.go +++ b/internal/postgres/password/scram.go @@ -1,7 +1,7 @@ package password /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/postgres/password/scram_test.go b/internal/postgres/password/scram_test.go index 7cdac419d0..4995191655 100644 --- a/internal/postgres/password/scram_test.go +++ b/internal/postgres/password/scram_test.go @@ -1,7 +1,7 @@ package password /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/tlsutil/primitives.go b/internal/tlsutil/primitives.go index 2ed4881e8e..363630fec1 100644 --- a/internal/tlsutil/primitives.go +++ b/internal/tlsutil/primitives.go @@ -1,7 +1,7 @@ package tlsutil /* -Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/tlsutil/primitives_test.go b/internal/tlsutil/primitives_test.go index 09d4dab6ce..684e4e3df6 100644 --- a/internal/tlsutil/primitives_test.go +++ b/internal/tlsutil/primitives_test.go @@ -1,7 +1,7 @@ package tlsutil /* -Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/backrest.go b/internal/util/backrest.go index a4b572f16b..50235ce0ad 100644 --- a/internal/util/backrest.go +++ b/internal/util/backrest.go @@ -1,7 +1,7 @@ package util /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/cluster.go b/internal/util/cluster.go index ef7796f439..c9d59d9bf6 100644 --- a/internal/util/cluster.go +++ b/internal/util/cluster.go @@ -1,7 +1,7 @@ package util /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/cluster_test.go b/internal/util/cluster_test.go index bf50277c8f..6bb8ea472a 100644 --- a/internal/util/cluster_test.go +++ b/internal/util/cluster_test.go @@ -1,7 +1,7 @@ package util /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/exporter.go b/internal/util/exporter.go index d46ad8cf53..6b4423b13a 100644 --- a/internal/util/exporter.go +++ b/internal/util/exporter.go @@ -1,7 +1,7 @@ package util /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/exporter_test.go b/internal/util/exporter_test.go index ffbde3a6e1..b614c4272d 100644 --- a/internal/util/exporter_test.go +++ b/internal/util/exporter_test.go @@ -1,7 +1,7 @@ package util /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/failover.go b/internal/util/failover.go index a19d887b25..37a899e7f8 100644 --- a/internal/util/failover.go +++ b/internal/util/failover.go @@ -1,7 +1,7 @@ package util /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/pgbouncer.go b/internal/util/pgbouncer.go index 0b3a9a528d..ff1033fada 100644 --- a/internal/util/pgbouncer.go +++ b/internal/util/pgbouncer.go @@ -1,7 +1,7 @@ package util /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/policy.go b/internal/util/policy.go index 25fe2953d7..5fe260ffef 100644 --- a/internal/util/policy.go +++ b/internal/util/policy.go @@ -1,7 +1,7 @@ package util /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/secrets.go b/internal/util/secrets.go index be8c2f4288..6a692ce377 100644 --- a/internal/util/secrets.go +++ b/internal/util/secrets.go @@ -1,7 +1,7 @@ package util /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/secrets_test.go b/internal/util/secrets_test.go index 423beb5e03..8dca7649bb 100644 --- a/internal/util/secrets_test.go +++ b/internal/util/secrets_test.go @@ -1,7 +1,7 @@ package util /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/ssh.go b/internal/util/ssh.go index e116716d12..17c4904753 100644 --- a/internal/util/ssh.go +++ b/internal/util/ssh.go @@ -1,7 +1,7 @@ package util /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/util.go b/internal/util/util.go index 3559d09894..532568e783 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -1,7 +1,7 @@ package util /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/cluster.go b/pkg/apis/crunchydata.com/v1/cluster.go index 4ad84d3b12..b8b88b4e1d 100644 --- a/pkg/apis/crunchydata.com/v1/cluster.go +++ b/pkg/apis/crunchydata.com/v1/cluster.go @@ -1,7 +1,7 @@ package v1 /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/cluster_test.go b/pkg/apis/crunchydata.com/v1/cluster_test.go index 4af663cf3e..8a31175cf4 100644 --- a/pkg/apis/crunchydata.com/v1/cluster_test.go +++ b/pkg/apis/crunchydata.com/v1/cluster_test.go @@ -1,7 +1,7 @@ package v1 /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/common.go b/pkg/apis/crunchydata.com/v1/common.go index 33818edf72..c768a0d408 100644 --- a/pkg/apis/crunchydata.com/v1/common.go +++ b/pkg/apis/crunchydata.com/v1/common.go @@ -1,7 +1,7 @@ package v1 /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/common_test.go b/pkg/apis/crunchydata.com/v1/common_test.go index 8ad909e64f..cde6832420 100644 --- a/pkg/apis/crunchydata.com/v1/common_test.go +++ b/pkg/apis/crunchydata.com/v1/common_test.go @@ -1,7 +1,7 @@ package v1 /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/doc.go b/pkg/apis/crunchydata.com/v1/doc.go index 4c793e782f..62cd5bc582 100644 --- a/pkg/apis/crunchydata.com/v1/doc.go +++ b/pkg/apis/crunchydata.com/v1/doc.go @@ -108,7 +108,7 @@ package v1 // +k8s:deepcopy-gen=package,register /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/errors.go b/pkg/apis/crunchydata.com/v1/errors.go index 6c8fddbb2d..9a1fbc30b1 100644 --- a/pkg/apis/crunchydata.com/v1/errors.go +++ b/pkg/apis/crunchydata.com/v1/errors.go @@ -3,7 +3,7 @@ package v1 import "errors" /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/policy.go b/pkg/apis/crunchydata.com/v1/policy.go index 904567d496..df08940188 100644 --- a/pkg/apis/crunchydata.com/v1/policy.go +++ b/pkg/apis/crunchydata.com/v1/policy.go @@ -1,7 +1,7 @@ package v1 /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/register.go b/pkg/apis/crunchydata.com/v1/register.go index 7b7359f504..586424126d 100644 --- a/pkg/apis/crunchydata.com/v1/register.go +++ b/pkg/apis/crunchydata.com/v1/register.go @@ -1,7 +1,7 @@ package v1 /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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/pkg/apis/crunchydata.com/v1/replica.go b/pkg/apis/crunchydata.com/v1/replica.go index 08a830a8d3..878ff63481 100644 --- a/pkg/apis/crunchydata.com/v1/replica.go +++ b/pkg/apis/crunchydata.com/v1/replica.go @@ -1,7 +1,7 @@ package v1 /* - Copyright 2018 - 2020 Crunchy Data Solutions, Inc. + Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/task.go b/pkg/apis/crunchydata.com/v1/task.go index d6791c8415..c7eb9e4605 100644 --- a/pkg/apis/crunchydata.com/v1/task.go +++ b/pkg/apis/crunchydata.com/v1/task.go @@ -1,7 +1,7 @@ package v1 /* - Copyright 2017 - 2020 Crunchy Data Solutions, Inc. + Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go b/pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go index 86b9b5ed4d..6534215bbf 100644 --- a/pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go +++ b/pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/backrestmsgs.go b/pkg/apiservermsgs/backrestmsgs.go index 29acd33a99..5a11e963bc 100644 --- a/pkg/apiservermsgs/backrestmsgs.go +++ b/pkg/apiservermsgs/backrestmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/catmsgs.go b/pkg/apiservermsgs/catmsgs.go index ded313371f..15f7d5cf85 100644 --- a/pkg/apiservermsgs/catmsgs.go +++ b/pkg/apiservermsgs/catmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/clustermsgs.go b/pkg/apiservermsgs/clustermsgs.go index d6c588eb56..d4287fd3cd 100644 --- a/pkg/apiservermsgs/clustermsgs.go +++ b/pkg/apiservermsgs/clustermsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/common.go b/pkg/apiservermsgs/common.go index 093405b4fd..d52499aa4a 100644 --- a/pkg/apiservermsgs/common.go +++ b/pkg/apiservermsgs/common.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/configmsgs.go b/pkg/apiservermsgs/configmsgs.go index 06ed680008..325e2281e5 100644 --- a/pkg/apiservermsgs/configmsgs.go +++ b/pkg/apiservermsgs/configmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/dfmsgs.go b/pkg/apiservermsgs/dfmsgs.go index 22541840e7..8d947768ef 100644 --- a/pkg/apiservermsgs/dfmsgs.go +++ b/pkg/apiservermsgs/dfmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/failovermsgs.go b/pkg/apiservermsgs/failovermsgs.go index bfeefcb49a..b51b11c3e4 100644 --- a/pkg/apiservermsgs/failovermsgs.go +++ b/pkg/apiservermsgs/failovermsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/labelmsgs.go b/pkg/apiservermsgs/labelmsgs.go index eabf3e8ecf..d0a914840e 100644 --- a/pkg/apiservermsgs/labelmsgs.go +++ b/pkg/apiservermsgs/labelmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/namespacemsgs.go b/pkg/apiservermsgs/namespacemsgs.go index 3921604a00..5fc4665a9b 100644 --- a/pkg/apiservermsgs/namespacemsgs.go +++ b/pkg/apiservermsgs/namespacemsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/pgadminmsgs.go b/pkg/apiservermsgs/pgadminmsgs.go index 5d68b9352d..73e4475294 100644 --- a/pkg/apiservermsgs/pgadminmsgs.go +++ b/pkg/apiservermsgs/pgadminmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/pgbouncermsgs.go b/pkg/apiservermsgs/pgbouncermsgs.go index e971e31424..9dd37ffb14 100644 --- a/pkg/apiservermsgs/pgbouncermsgs.go +++ b/pkg/apiservermsgs/pgbouncermsgs.go @@ -3,7 +3,7 @@ package apiservermsgs import v1 "k8s.io/api/core/v1" /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/pgdumpmsgs.go b/pkg/apiservermsgs/pgdumpmsgs.go index 3afb00955a..c83269648a 100644 --- a/pkg/apiservermsgs/pgdumpmsgs.go +++ b/pkg/apiservermsgs/pgdumpmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/pgorolemsgs.go b/pkg/apiservermsgs/pgorolemsgs.go index 1f62efa1ab..6aae3494d0 100644 --- a/pkg/apiservermsgs/pgorolemsgs.go +++ b/pkg/apiservermsgs/pgorolemsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/pgousermsgs.go b/pkg/apiservermsgs/pgousermsgs.go index 815f8f1fdf..4690c1f888 100644 --- a/pkg/apiservermsgs/pgousermsgs.go +++ b/pkg/apiservermsgs/pgousermsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/policymsgs.go b/pkg/apiservermsgs/policymsgs.go index ba5c6cea21..023676cf6b 100644 --- a/pkg/apiservermsgs/policymsgs.go +++ b/pkg/apiservermsgs/policymsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/pvcmsgs.go b/pkg/apiservermsgs/pvcmsgs.go index f59ddd7983..da902da96b 100644 --- a/pkg/apiservermsgs/pvcmsgs.go +++ b/pkg/apiservermsgs/pvcmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/reloadmsgs.go b/pkg/apiservermsgs/reloadmsgs.go index 34a3738399..11c4293980 100644 --- a/pkg/apiservermsgs/reloadmsgs.go +++ b/pkg/apiservermsgs/reloadmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/restartmsgs.go b/pkg/apiservermsgs/restartmsgs.go index a36307739c..e9bf5b8b57 100644 --- a/pkg/apiservermsgs/restartmsgs.go +++ b/pkg/apiservermsgs/restartmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/schedulemsgs.go b/pkg/apiservermsgs/schedulemsgs.go index 4b037a5992..47e9f90ca8 100644 --- a/pkg/apiservermsgs/schedulemsgs.go +++ b/pkg/apiservermsgs/schedulemsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/statusmsgs.go b/pkg/apiservermsgs/statusmsgs.go index 94994c75b9..72a6c79aab 100644 --- a/pkg/apiservermsgs/statusmsgs.go +++ b/pkg/apiservermsgs/statusmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/upgrademsgs.go b/pkg/apiservermsgs/upgrademsgs.go index ab7fecc47a..a360c036c7 100644 --- a/pkg/apiservermsgs/upgrademsgs.go +++ b/pkg/apiservermsgs/upgrademsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/usermsgs.go b/pkg/apiservermsgs/usermsgs.go index 9c63c3d483..4a716966ba 100644 --- a/pkg/apiservermsgs/usermsgs.go +++ b/pkg/apiservermsgs/usermsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/usermsgs_test.go b/pkg/apiservermsgs/usermsgs_test.go index 207eda3757..d6cc93b2ca 100644 --- a/pkg/apiservermsgs/usermsgs_test.go +++ b/pkg/apiservermsgs/usermsgs_test.go @@ -1,7 +1,7 @@ package apiservermsgs /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/versionmsgs.go b/pkg/apiservermsgs/versionmsgs.go index 7685221c44..38ab640cdb 100644 --- a/pkg/apiservermsgs/versionmsgs.go +++ b/pkg/apiservermsgs/versionmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +Copyright 2017 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/watchmsgs.go b/pkg/apiservermsgs/watchmsgs.go index 9d50a81ccd..02c1472b90 100644 --- a/pkg/apiservermsgs/watchmsgs.go +++ b/pkg/apiservermsgs/watchmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2019 - 2020 Crunchy Data Solutions, Inc. +Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/workflowmsgs.go b/pkg/apiservermsgs/workflowmsgs.go index 2908d75347..3d4c44353a 100644 --- a/pkg/apiservermsgs/workflowmsgs.go +++ b/pkg/apiservermsgs/workflowmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +Copyright 2018 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/events/eventing.go b/pkg/events/eventing.go index 5c40861352..1a13b932f1 100644 --- a/pkg/events/eventing.go +++ b/pkg/events/eventing.go @@ -1,7 +1,7 @@ package events /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/events/eventtype.go b/pkg/events/eventtype.go index 95a09f8121..b93159e77e 100644 --- a/pkg/events/eventtype.go +++ b/pkg/events/eventtype.go @@ -1,7 +1,7 @@ package events /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/events/pgoeventtype.go b/pkg/events/pgoeventtype.go index 75c076b311..4e4f114868 100644 --- a/pkg/events/pgoeventtype.go +++ b/pkg/events/pgoeventtype.go @@ -1,7 +1,7 @@ package events /* - Copyright 2019 - 2020 Crunchy Data Solutions, Inc. + Copyright 2019 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/clientset.go b/pkg/generated/clientset/versioned/clientset.go index 061bbaed95..7a20991345 100644 --- a/pkg/generated/clientset/versioned/clientset.go +++ b/pkg/generated/clientset/versioned/clientset.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/doc.go b/pkg/generated/clientset/versioned/doc.go index e2534c0fe7..f862afa1b0 100644 --- a/pkg/generated/clientset/versioned/doc.go +++ b/pkg/generated/clientset/versioned/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/fake/clientset_generated.go b/pkg/generated/clientset/versioned/fake/clientset_generated.go index 384d0e7737..5f9ec9bbbb 100644 --- a/pkg/generated/clientset/versioned/fake/clientset_generated.go +++ b/pkg/generated/clientset/versioned/fake/clientset_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/fake/doc.go b/pkg/generated/clientset/versioned/fake/doc.go index 6318a06f3c..e9300efbfe 100644 --- a/pkg/generated/clientset/versioned/fake/doc.go +++ b/pkg/generated/clientset/versioned/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/fake/register.go b/pkg/generated/clientset/versioned/fake/register.go index 62032825ad..d33f1544e9 100644 --- a/pkg/generated/clientset/versioned/fake/register.go +++ b/pkg/generated/clientset/versioned/fake/register.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/scheme/doc.go b/pkg/generated/clientset/versioned/scheme/doc.go index 462fec5e30..49cfafd10d 100644 --- a/pkg/generated/clientset/versioned/scheme/doc.go +++ b/pkg/generated/clientset/versioned/scheme/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/scheme/register.go b/pkg/generated/clientset/versioned/scheme/register.go index 4850f74045..2ce45ab80b 100644 --- a/pkg/generated/clientset/versioned/scheme/register.go +++ b/pkg/generated/clientset/versioned/scheme/register.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/crunchydata.com_client.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/crunchydata.com_client.go index aac71b2aa3..4c862528fa 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/crunchydata.com_client.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/crunchydata.com_client.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/doc.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/doc.go index b7311c21af..21c249ea20 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/doc.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/doc.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/doc.go index 759d8fff95..14f506a6fb 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/doc.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_crunchydata.com_client.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_crunchydata.com_client.go index f8d6b6b350..33ad7a5550 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_crunchydata.com_client.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_crunchydata.com_client.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgcluster.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgcluster.go index 177fe4240c..ff11262ddf 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgcluster.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgcluster.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgpolicy.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgpolicy.go index 746a49a17c..5a661bb23a 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgpolicy.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgpolicy.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgreplica.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgreplica.go index 70a1e8a559..05708f6b48 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgreplica.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgreplica.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgtask.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgtask.go index 6ec34a55fd..df8a0479cd 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgtask.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgtask.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/generated_expansion.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/generated_expansion.go index 066f811e51..5ea3a63db1 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/generated_expansion.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/generated_expansion.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgcluster.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgcluster.go index 6ccbb22d73..45d3777e84 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgcluster.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgcluster.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgpolicy.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgpolicy.go index 1d9711033c..8dbd0227c7 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgpolicy.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgpolicy.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgreplica.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgreplica.go index f9ffed63eb..c9c553db30 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgreplica.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgreplica.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgtask.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgtask.go index 5971a76095..e1095c71e9 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgtask.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgtask.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/informers/externalversions/crunchydata.com/interface.go b/pkg/generated/informers/externalversions/crunchydata.com/interface.go index dfe44a0fcb..698763aff3 100644 --- a/pkg/generated/informers/externalversions/crunchydata.com/interface.go +++ b/pkg/generated/informers/externalversions/crunchydata.com/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/informers/externalversions/crunchydata.com/v1/interface.go b/pkg/generated/informers/externalversions/crunchydata.com/v1/interface.go index c34a37f8e7..b30e24b239 100644 --- a/pkg/generated/informers/externalversions/crunchydata.com/v1/interface.go +++ b/pkg/generated/informers/externalversions/crunchydata.com/v1/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/informers/externalversions/crunchydata.com/v1/pgcluster.go b/pkg/generated/informers/externalversions/crunchydata.com/v1/pgcluster.go index c11596abe9..1e9753596d 100644 --- a/pkg/generated/informers/externalversions/crunchydata.com/v1/pgcluster.go +++ b/pkg/generated/informers/externalversions/crunchydata.com/v1/pgcluster.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/informers/externalversions/crunchydata.com/v1/pgpolicy.go b/pkg/generated/informers/externalversions/crunchydata.com/v1/pgpolicy.go index 2016ae2a2e..741ad3c39e 100644 --- a/pkg/generated/informers/externalversions/crunchydata.com/v1/pgpolicy.go +++ b/pkg/generated/informers/externalversions/crunchydata.com/v1/pgpolicy.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/informers/externalversions/crunchydata.com/v1/pgreplica.go b/pkg/generated/informers/externalversions/crunchydata.com/v1/pgreplica.go index 9387d0937b..c7946ec142 100644 --- a/pkg/generated/informers/externalversions/crunchydata.com/v1/pgreplica.go +++ b/pkg/generated/informers/externalversions/crunchydata.com/v1/pgreplica.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/informers/externalversions/crunchydata.com/v1/pgtask.go b/pkg/generated/informers/externalversions/crunchydata.com/v1/pgtask.go index d08c342305..44398863fa 100644 --- a/pkg/generated/informers/externalversions/crunchydata.com/v1/pgtask.go +++ b/pkg/generated/informers/externalversions/crunchydata.com/v1/pgtask.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/informers/externalversions/factory.go b/pkg/generated/informers/externalversions/factory.go index 56886a005a..65c18752b4 100644 --- a/pkg/generated/informers/externalversions/factory.go +++ b/pkg/generated/informers/externalversions/factory.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/informers/externalversions/generic.go b/pkg/generated/informers/externalversions/generic.go index 130dd5ad37..48e7491d80 100644 --- a/pkg/generated/informers/externalversions/generic.go +++ b/pkg/generated/informers/externalversions/generic.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go b/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go index 4086ab3a09..130bc043a8 100644 --- a/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go +++ b/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/listers/crunchydata.com/v1/expansion_generated.go b/pkg/generated/listers/crunchydata.com/v1/expansion_generated.go index ca6b77b1a3..369c56b717 100644 --- a/pkg/generated/listers/crunchydata.com/v1/expansion_generated.go +++ b/pkg/generated/listers/crunchydata.com/v1/expansion_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/listers/crunchydata.com/v1/pgcluster.go b/pkg/generated/listers/crunchydata.com/v1/pgcluster.go index 4dd8121f86..7bcf7f4328 100644 --- a/pkg/generated/listers/crunchydata.com/v1/pgcluster.go +++ b/pkg/generated/listers/crunchydata.com/v1/pgcluster.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/listers/crunchydata.com/v1/pgpolicy.go b/pkg/generated/listers/crunchydata.com/v1/pgpolicy.go index 03740c4b71..f4b39358a0 100644 --- a/pkg/generated/listers/crunchydata.com/v1/pgpolicy.go +++ b/pkg/generated/listers/crunchydata.com/v1/pgpolicy.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/listers/crunchydata.com/v1/pgreplica.go b/pkg/generated/listers/crunchydata.com/v1/pgreplica.go index b6cee83186..f3bdf3bd68 100644 --- a/pkg/generated/listers/crunchydata.com/v1/pgreplica.go +++ b/pkg/generated/listers/crunchydata.com/v1/pgreplica.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/listers/crunchydata.com/v1/pgtask.go b/pkg/generated/listers/crunchydata.com/v1/pgtask.go index c7d30868a8..1a46df7b78 100644 --- a/pkg/generated/listers/crunchydata.com/v1/pgtask.go +++ b/pkg/generated/listers/crunchydata.com/v1/pgtask.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Crunchy Data Solutions, Inc. +Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/pv/create-pv-nfs-label.sh b/pv/create-pv-nfs-label.sh index a77e3e68e3..a347a907fd 100755 --- a/pv/create-pv-nfs-label.sh +++ b/pv/create-pv-nfs-label.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2018 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/pv/create-pv-nfs-legacy.sh b/pv/create-pv-nfs-legacy.sh index 4850e73652..96d698e159 100755 --- a/pv/create-pv-nfs-legacy.sh +++ b/pv/create-pv-nfs-legacy.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/pv/create-pv-nfs.sh b/pv/create-pv-nfs.sh index 8b2ef4ab67..e1e71c95d8 100755 --- a/pv/create-pv-nfs.sh +++ b/pv/create-pv-nfs.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/pv/create-pv.sh b/pv/create-pv.sh index 46bbf4dbe8..6d9ede0b71 100755 --- a/pv/create-pv.sh +++ b/pv/create-pv.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/pv/delete-pv.sh b/pv/delete-pv.sh index cd653a1778..b3d7422ff2 100755 --- a/pv/delete-pv.sh +++ b/pv/delete-pv.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 - 2020 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2021 Crunchy Data Solutions, Inc. # 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 diff --git a/testing/pgo_cli/cluster_backup_test.go b/testing/pgo_cli/cluster_backup_test.go index d2f8508c3d..ceeefe2e5a 100644 --- a/testing/pgo_cli/cluster_backup_test.go +++ b/testing/pgo_cli/cluster_backup_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_cat_test.go b/testing/pgo_cli/cluster_cat_test.go index 4cb159be8d..aea958fb15 100644 --- a/testing/pgo_cli/cluster_cat_test.go +++ b/testing/pgo_cli/cluster_cat_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_create_test.go b/testing/pgo_cli/cluster_create_test.go index 26f0c2be4f..f0579de8cd 100644 --- a/testing/pgo_cli/cluster_create_test.go +++ b/testing/pgo_cli/cluster_create_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_delete_test.go b/testing/pgo_cli/cluster_delete_test.go index cba99408f7..1285b6026e 100644 --- a/testing/pgo_cli/cluster_delete_test.go +++ b/testing/pgo_cli/cluster_delete_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_df_test.go b/testing/pgo_cli/cluster_df_test.go index 8171a7aa45..91ae0b8092 100644 --- a/testing/pgo_cli/cluster_df_test.go +++ b/testing/pgo_cli/cluster_df_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_failover_test.go b/testing/pgo_cli/cluster_failover_test.go index d35a6d87f5..ac4f2a40c6 100644 --- a/testing/pgo_cli/cluster_failover_test.go +++ b/testing/pgo_cli/cluster_failover_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_label_test.go b/testing/pgo_cli/cluster_label_test.go index 0f54f7af93..ccf4a17461 100644 --- a/testing/pgo_cli/cluster_label_test.go +++ b/testing/pgo_cli/cluster_label_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_pgbouncer_test.go b/testing/pgo_cli/cluster_pgbouncer_test.go index 9c5b72ba66..b0f9199881 100644 --- a/testing/pgo_cli/cluster_pgbouncer_test.go +++ b/testing/pgo_cli/cluster_pgbouncer_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_policy_test.go b/testing/pgo_cli/cluster_policy_test.go index df7197deb4..66db7c6080 100644 --- a/testing/pgo_cli/cluster_policy_test.go +++ b/testing/pgo_cli/cluster_policy_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_pvc_test.go b/testing/pgo_cli/cluster_pvc_test.go index bd91b28435..0009225e4a 100644 --- a/testing/pgo_cli/cluster_pvc_test.go +++ b/testing/pgo_cli/cluster_pvc_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_reload_test.go b/testing/pgo_cli/cluster_reload_test.go index e2900ee4fb..02cf63a479 100644 --- a/testing/pgo_cli/cluster_reload_test.go +++ b/testing/pgo_cli/cluster_reload_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_restart_test.go b/testing/pgo_cli/cluster_restart_test.go index 9daeea644e..c46763731a 100644 --- a/testing/pgo_cli/cluster_restart_test.go +++ b/testing/pgo_cli/cluster_restart_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_scale_test.go b/testing/pgo_cli/cluster_scale_test.go index 11ce9a9c21..219e44f582 100644 --- a/testing/pgo_cli/cluster_scale_test.go +++ b/testing/pgo_cli/cluster_scale_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_scaledown_test.go b/testing/pgo_cli/cluster_scaledown_test.go index f1926a4d4d..5e9dc16b28 100644 --- a/testing/pgo_cli/cluster_scaledown_test.go +++ b/testing/pgo_cli/cluster_scaledown_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_test_test.go b/testing/pgo_cli/cluster_test_test.go index 153d47f467..76100eb9f0 100644 --- a/testing/pgo_cli/cluster_test_test.go +++ b/testing/pgo_cli/cluster_test_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_user_test.go b/testing/pgo_cli/cluster_user_test.go index 9e59757a9a..964bb6b58c 100644 --- a/testing/pgo_cli/cluster_user_test.go +++ b/testing/pgo_cli/cluster_user_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/operator_namespace_test.go b/testing/pgo_cli/operator_namespace_test.go index ef327c2ece..57bc685ea2 100644 --- a/testing/pgo_cli/operator_namespace_test.go +++ b/testing/pgo_cli/operator_namespace_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/operator_rbac_test.go b/testing/pgo_cli/operator_rbac_test.go index 8fa4609894..569f1bd090 100644 --- a/testing/pgo_cli/operator_rbac_test.go +++ b/testing/pgo_cli/operator_rbac_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/operator_test.go b/testing/pgo_cli/operator_test.go index 743b872614..09e8148c68 100644 --- a/testing/pgo_cli/operator_test.go +++ b/testing/pgo_cli/operator_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/suite_helpers_test.go b/testing/pgo_cli/suite_helpers_test.go index dcaee7b0f0..57f3a7ca4a 100644 --- a/testing/pgo_cli/suite_helpers_test.go +++ b/testing/pgo_cli/suite_helpers_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/suite_pgo_cmd_test.go b/testing/pgo_cli/suite_pgo_cmd_test.go index 91aec62228..245d314aa7 100644 --- a/testing/pgo_cli/suite_pgo_cmd_test.go +++ b/testing/pgo_cli/suite_pgo_cmd_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/suite_test.go b/testing/pgo_cli/suite_test.go index 4f2056d08e..9429f28278 100644 --- a/testing/pgo_cli/suite_test.go +++ b/testing/pgo_cli/suite_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 Crunchy Data Solutions, Inc. + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. 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 From 8556371512223c67796c83bf0308e018c843c21e Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sat, 2 Jan 2021 12:46:02 -0500 Subject: [PATCH 107/276] Update explanation of how the default storage configuration works This now reflects how the Operator actually uses default storage classes in deployment environments. --- docs/content/installation/configuration.md | 26 ++++++++++------------ 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/docs/content/installation/configuration.md b/docs/content/installation/configuration.md index ce097d2753..d984bc34bd 100644 --- a/docs/content/installation/configuration.md +++ b/docs/content/installation/configuration.md @@ -129,30 +129,28 @@ unique ID for each required storage configuration. You can specify the default storage to use for PostgreSQL, pgBackRest, and other elements that require storage that can outlast the lifetime of a Pod. While the -PostgreSQL Operator defaults to using `hostpathstorage` to work with -environments that are typically used to test, we recommend using one of the -other storage classes in production deployments. +PostgreSQL Operator defaults to using `default` to work with the default storage +class available in your environment. | Name | Default | Required | Description | |------|---------|----------|-------------| -| `backrest_storage` | hostpathstorage | **Required** | Set the value of the storage configuration to use for the pgbackrest shared repository deployment created when a user specifies pgbackrest to be enabled on a cluster. | -| `backup_storage` | hostpathstorage | **Required** | Set the value of the storage configuration to use for backups, including the storage for pgbackrest repo volumes. | -| `primary_storage` | hostpathstorage | **Required** | Set to configure which storage definition to use when creating volumes used by PostgreSQL primaries on all newly created clusters. | -| `replica_storage` | hostpathstorage | **Required** | Set to configure which storage definition to use when creating volumes used by PostgreSQL replicas on all newly created clusters. | +| `backrest_storage` | default | **Required** | Set the value of the storage configuration to use for the pgbackrest shared repository deployment created when a user specifies pgbackrest to be enabled on a cluster. | +| `backup_storage` | default | **Required** | Set the value of the storage configuration to use for backups, including the storage for pgbackrest repo volumes. | +| `primary_storage` | default | **Required** | Set to configure which storage definition to use when creating volumes used by PostgreSQL primaries on all newly created clusters. | +| `replica_storage` | default | **Required** | Set to configure which storage definition to use when creating volumes used by PostgreSQL replicas on all newly created clusters. | | `wal_storage` | | | Set to configure which storage definition to use when creating volumes used for PostgreSQL Write-Ahead Log. | #### Example Defaults ```yaml -backrest_storage: 'nfsstorage' -backup_storage: 'nfsstorage' -primary_storage: 'nfsstorage' -replica_storage: 'nfsstorage' +backrest_storage: default +backup_storage: default +primary_storage: default +replica_storage: default ``` -With the configuration shown above, the `nfsstorage` storage configuration would -be used by default for the various containers created for a PG cluster -(i.e. containers for the primary DB, replica DB's, backups and/or `pgBackRest`). +With the configuration shown above, the default storage class available in the +deployment environment is used. ### Considerations for Multi-Zone Cloud Environments From 3f9026aff26d6bdb4719f6ed75012599938600b2 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sat, 2 Jan 2021 13:34:17 -0500 Subject: [PATCH 108/276] Update attribute name formatting in custom resource docs The name formatting now matches what the actual specifications look like. --- docs/content/custom-resources/_index.md | 152 ++++++++++++------------ 1 file changed, 77 insertions(+), 75 deletions(-) diff --git a/docs/content/custom-resources/_index.md b/docs/content/custom-resources/_index.md index 3559d0eb14..370a0aee25 100644 --- a/docs/content/custom-resources/_index.md +++ b/docs/content/custom-resources/_index.md @@ -721,53 +721,54 @@ make changes, as described below. | Attribute | Action | Description | |-----------|--------|-------------| -| Annotations | `create`, `update` | Specify Kubernetes [Annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) that can be applied to the different deployments managed by the PostgreSQL Operator (PostgreSQL, pgBackRest, pgBouncer). For more information, please see the "Annotations Specification" below. | -| BackrestConfig | `create` | Optional references to pgBackRest configuration files -| BackrestLimits | `create`, `update` | Specify the container resource limits that the pgBackRest repository should use. Follows the [Kubernetes definitions of resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | +| annotations | `create`, `update` | Specify Kubernetes [Annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) that can be applied to the different deployments managed by the PostgreSQL Operator (PostgreSQL, pgBackRest, pgBouncer). For more information, please see the "Annotations Specification" below. | +| backrestConfig | `create` | Optional references to pgBackRest configuration files | +| backrestLimits | `create`, `update` | Specify the container resource limits that the pgBackRest repository should use. Follows the [Kubernetes definitions of resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | +| backrestRepoPath | `create` | Optional reference to the location of the pgBackRest repository. | | BackrestResources | `create`, `update` | Specify the container resource requests that the pgBackRest repository should use. Follows the [Kubernetes definitions of resource requests](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | -| BackrestS3Bucket | `create` | An optional parameter that specifies a S3 bucket that pgBackRest should use. | -| BackrestS3Endpoint | `create` | An optional parameter that specifies the S3 endpoint pgBackRest should use. | -| BackrestS3Region | `create` | An optional parameter that specifies a cloud region that pgBackRest should use. | -| BackrestStorageTypes | `create` | An optional parameter that takes an array of different repositories types that can be used to store pgBackRest backups. Choices are `posix` and `s3`. If nothing is specified, it defaults to `posix`. (`local`, equivalent to `posix`, is available for backwards compatibility).| -| BackrestS3URIStyle | `create` | An optional parameter that specifies if pgBackRest should use the `path` or `host` S3 URI style. | -| BackrestS3VerifyTLS | `create` | An optional parameter that specifies if pgBackRest should verify the TLS endpoint. | +| backrestS3Bucket | `create` | An optional parameter that specifies a S3 bucket that pgBackRest should use. | +| backrestS3Endpoint | `create` | An optional parameter that specifies the S3 endpoint pgBackRest should use. | +| backrestS3Region | `create` | An optional parameter that specifies a cloud region that pgBackRest should use. | +| backrestS3URIStyle | `create` | An optional parameter that specifies if pgBackRest should use the `path` or `host` S3 URI style. | +| backrestS3VerifyTLS | `create` | An optional parameter that specifies if pgBackRest should verify the TLS endpoint. | | BackrestStorage | `create` | A specification that gives information about the storage attributes for the pgBackRest repository, which stores backups and archives, of the PostgreSQL cluster. For details, please see the `Storage Specification` section below. This is required. | -| CCPImage | `create` | The name of the PostgreSQL container image to use, e.g. `crunchy-postgres-ha` or `crunchy-postgres-ha-gis`. | -| CCPImagePrefix | `create` | If provided, the image prefix (or registry) of the PostgreSQL container image, e.g. `registry.developers.crunchydata.com/crunchydata`. The default is to use the image prefix set in the PostgreSQL Operator configuration. | -| CCPImageTag | `create` | The tag of the PostgreSQL container image to use, e.g. `{{< param centosBase >}}-{{< param postgresVersion >}}-{{< param operatorVersion >}}`. | -| ClusterName | `create` | The name of the PostgreSQL cluster, e.g. `hippo`. This is used to group PostgreSQL instances (primary, replicas) together. | -| CustomConfig | `create` | If specified, references a custom ConfigMap to use when bootstrapping a PostgreSQL cluster. For the shape of this file, please see the section on [Custom Configuration]({{< relref "/advanced/custom-configuration.md" >}}) | -| Database | `create` | The name of a database that the PostgreSQL user can log into after the PostgreSQL cluster is created. | -| DisableAutofail | `create`, `update` | If set to true, disables the high availability capabilities of a PostgreSQL cluster. By default, every cluster can have high availability if there is at least one replica. | -| ExporterLimits | `create`, `update` | Specify the container resource limits that the `crunchy-postgres-exporter` sidecar uses when it is deployed with a PostgreSQL instance. Follows the [Kubernetes definitions of resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | -| Exporter | `create`,`update` | If `true`, deploys the `crunchy-postgres-exporter` sidecar for metrics collection | -| ExporterPort | `create` | If `Exporter` is `true`, then this specifies the port that the metrics sidecar runs on (e.g. `9187`) | -| ExporterResources | `create`, `update` | Specify the container resource requests that the `crunchy-postgres-exporter` sidecar uses when it is deployed with a PostgreSQL instance. Follows the [Kubernetes definitions of resource requests](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | -| Limits | `create`, `update` | Specify the container resource limits that the PostgreSQL cluster should use. Follows the [Kubernetes definitions of resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | -| Name | `create` | The name of the PostgreSQL instance that is the primary. On creation, this should be set to be the same as `ClusterName`. | -| Namespace | `create` | The Kubernetes Namespace that the PostgreSQL cluster is deployed in. | -| NodeAffinity | `create` | Sets the [node affinity rules]({{< relref "/architecture/high-availability/_index.md#node-affinity" >}}) for the PostgreSQL cluster and associated PostgreSQL instances. Can be overridden on a per-instance (`pgreplicas.crunchydata.com`) basis. Please see the `Node Affinity Specification` section below. | -| PGBadger | `create`,`update` | If `true`, deploys the `crunchy-pgbadger` sidecar for query analysis. | -| PGBadgerPort | `create` | If the `PGBadger` label is set, then this specifies the port that the pgBadger sidecar runs on (e.g. `10000`) | -| PGDataSource | `create` | Used to indicate if a PostgreSQL cluster should bootstrap its data from a pgBackRest repository. This uses the PostgreSQL Data Source Specification, described below. | -| PGOImagePrefix | `create` | If provided, the image prefix (or registry) of any PostgreSQL Operator images that are used for jobs, e.g. `registry.developers.crunchydata.com/crunchydata`. The default is to use the image prefix set in the PostgreSQL Operator configuration. | -| PgBouncer | `create`, `update` | If specified, defines the attributes to use for the pgBouncer connection pooling deployment that can be used in conjunction with this PostgreSQL cluster. Please see the specification defined below. | -| PodAntiAffinity | `create` | A required section. Sets the [pod anti-affinity rules]({{< relref "/architecture/high-availability/_index.md#how-the-crunchy-postgresql-operator-uses-pod-anti-affinity" >}}) for the PostgreSQL cluster and associated deployments. Please see the `Pod Anti-Affinity Specification` section below. | -| Policies | `create` | If provided, a comma-separated list referring to `pgpolicies.crunchydata.com.Spec.Name` that should be run once the PostgreSQL primary is first initialized. | -| Port | `create` | The port that PostgreSQL will run on, e.g. `5432`. | +| backrestStorageTypes | `create` | An optional parameter that takes an array of different repositories types that can be used to store pgBackRest backups. Choices are `posix` and `s3`. If nothing is specified, it defaults to `posix`. (`local`, equivalent to `posix`, is available for backwards compatibility).| +| ccpimage | `create` | The name of the PostgreSQL container image to use, e.g. `crunchy-postgres-ha` or `crunchy-postgres-ha-gis`. | +| ccpimageprefix | `create` | If provided, the image prefix (or registry) of the PostgreSQL container image, e.g. `registry.developers.crunchydata.com/crunchydata`. The default is to use the image prefix set in the PostgreSQL Operator configuration. | +| ccpimagetag | `create` | The tag of the PostgreSQL container image to use, e.g. `{{< param centosBase >}}-{{< param postgresVersion >}}-{{< param operatorVersion >}}`. | +| clustername | `create` | The name of the PostgreSQL cluster, e.g. `hippo`. This is used to group PostgreSQL instances (primary, replicas) together. | +| customconfig | `create` | If specified, references a custom ConfigMap to use when bootstrapping a PostgreSQL cluster. For the shape of this file, please see the section on [Custom Configuration]({{< relref "/advanced/custom-configuration.md" >}}) | +| database | `create` | The name of a database that the PostgreSQL user can log into after the PostgreSQL cluster is created. | +| disableAutofail | `create`, `update` | If set to true, disables the high availability capabilities of a PostgreSQL cluster. By default, every cluster can have high availability if there is at least one replica. | +| exporter | `create`,`update` | If `true`, deploys the `crunchy-postgres-exporter` sidecar for metrics collection | +| exporterLimits | `create`, `update` | Specify the container resource limits that the `crunchy-postgres-exporter` sidecar uses when it is deployed with a PostgreSQL instance. Follows the [Kubernetes definitions of resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | +| exporterport | `create` | If `Exporter` is `true`, then this specifies the port that the metrics sidecar runs on (e.g. `9187`) | +| exporterResources | `create`, `update` | Specify the container resource requests that the `crunchy-postgres-exporter` sidecar uses when it is deployed with a PostgreSQL instance. Follows the [Kubernetes definitions of resource requests](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | +| limits | `create`, `update` | Specify the container resource limits that the PostgreSQL cluster should use. Follows the [Kubernetes definitions of resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | +| name | `create` | The name of the PostgreSQL instance that is the primary. On creation, this should be set to be the same as `ClusterName`. | +| namespace | `create` | The Kubernetes Namespace that the PostgreSQL cluster is deployed in. | +| nodeAffinity | `create` | Sets the [node affinity rules]({{< relref "/architecture/high-availability/_index.md#node-affinity" >}}) for the PostgreSQL cluster and associated PostgreSQL instances. Can be overridden on a per-instance (`pgreplicas.crunchydata.com`) basis. Please see the `Node Affinity Specification` section below. | +| pgBadger | `create`,`update` | If `true`, deploys the `crunchy-pgbadger` sidecar for query analysis. | +| pgbadgerport | `create` | If the `PGBadger` label is set, then this specifies the port that the pgBadger sidecar runs on (e.g. `10000`) | +| pgBouncer | `create`, `update` | If specified, defines the attributes to use for the pgBouncer connection pooling deployment that can be used in conjunction with this PostgreSQL cluster. Please see the specification defined below. | +| pgDataSource | `create` | Used to indicate if a PostgreSQL cluster should bootstrap its data from a pgBackRest repository. This uses the PostgreSQL Data Source Specification, described below. | +| pgoimageprefix | `create` | If provided, the image prefix (or registry) of any PostgreSQL Operator images that are used for jobs, e.g. `registry.developers.crunchydata.com/crunchydata`. The default is to use the image prefix set in the PostgreSQL Operator configuration. | +| podAntiAffinity | `create` | A required section. Sets the [pod anti-affinity rules]({{< relref "/architecture/high-availability/_index.md#how-the-crunchy-postgresql-operator-uses-pod-anti-affinity" >}}) for the PostgreSQL cluster and associated deployments. Please see the `Pod Anti-Affinity Specification` section below. | +| policies | `create` | If provided, a comma-separated list referring to `pgpolicies.crunchydata.com.Spec.Name` that should be run once the PostgreSQL primary is first initialized. | +| port | `create` | The port that PostgreSQL will run on, e.g. `5432`. | | ReplicaStorage | `create` | A specification that gives information about the storage attributes for any replicas in the PostgreSQL cluster. For details, please see the `Storage Specification` section below. This will likely be changed in the future based on the nature of the high-availability system, but presently it is still required that you set it. It is recommended you use similar settings to that of `PrimaryStorage`. | -| Replicas | `create` | The number of replicas to create after a PostgreSQL primary is first initialized. This only works on create; to scale a cluster after it is initialized, please use the [`pgo scale`]({{< relref "/pgo-client/reference/pgo_scale.md" >}}) command. | -| Resources | `create`, `update` | Specify the container resource requests that the PostgreSQL cluster should use. Follows the [Kubernetes definitions of resource requests](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | -| ServiceType | `create`, `update` | Sets the Kubernetes [Service](https://kubernetes.io/docs/concepts/services-networking/service/) type to use for the cluster. If not set, defaults to `ClusterIP`. | -| SyncReplication | `create` | If set to `true`, specifies the PostgreSQL cluster to use [synchronous replication]({{< relref "/architecture/high-availability/_index.md#how-the-crunchy-postgresql-operator-uses-pod-anti-affinity#synchronous-replication-guarding-against-transactions-loss" >}}).| -| User | `create` | The name of the PostgreSQL user that is created when the PostgreSQL cluster is first created. | -| UserLabels | `create` | A set of key-value string pairs that are used as a sort of "catch-all" as well as a way to add custom labels to clusters. This will disappear at some point. | -| TablespaceMounts | `create`,`update` | Lists any tablespaces that are attached to the PostgreSQL cluster. Tablespaces can be added at a later time by updating the `TablespaceMounts` entry, but they cannot be removed. Stores a map of information, with the key being the name of the tablespace, and the value being a Storage Specification, defined below. | -| TLS | `create` | Defines the attributes for enabling TLS for a PostgreSQL cluster. See TLS Specification below. | -| TLSOnly | `create` | If set to true, requires client connections to use only TLS to connect to the PostgreSQL database. | -| Tolerations | `create`,`update` | Any array of Kubernetes [Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/). Please refer to the [Kubernetes documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) for how to set this field. | -| Standby | `create`, `update` | If set to true, indicates that the PostgreSQL cluster is a "standby" cluster, i.e. is in read-only mode entirely. Please see [Kubernetes Multi-Cluster Deployments]({{< relref "/architecture/high-availability/multi-cluster-kubernetes.md" >}}) for more information. | -| Shutdown | `create`, `update` | If set to true, indicates that a PostgreSQL cluster should shutdown. If set to false, indicates that a PostgreSQL cluster should be up and running. | +| replicas | `create` | The number of replicas to create after a PostgreSQL primary is first initialized. This only works on create; to scale a cluster after it is initialized, please use the [`pgo scale`]({{< relref "/pgo-client/reference/pgo_scale.md" >}}) command. | +| resources | `create`, `update` | Specify the container resource requests that the PostgreSQL cluster should use. Follows the [Kubernetes definitions of resource requests](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | +| serviceType | `create`, `update` | Sets the Kubernetes [Service](https://kubernetes.io/docs/concepts/services-networking/service/) type to use for the cluster. If not set, defaults to `ClusterIP`. | +| shutdown | `create`, `update` | If set to true, indicates that a PostgreSQL cluster should shutdown. If set to false, indicates that a PostgreSQL cluster should be up and running. | +| standby | `create`, `update` | If set to true, indicates that the PostgreSQL cluster is a "standby" cluster, i.e. is in read-only mode entirely. Please see [Kubernetes Multi-Cluster Deployments]({{< relref "/architecture/high-availability/multi-cluster-kubernetes.md" >}}) for more information. | +| syncReplication | `create` | If set to `true`, specifies the PostgreSQL cluster to use [synchronous replication]({{< relref "/architecture/high-availability/_index.md#how-the-crunchy-postgresql-operator-uses-pod-anti-affinity#synchronous-replication-guarding-against-transactions-loss" >}}).| +| tablespaceMounts | `create`,`update` | Lists any tablespaces that are attached to the PostgreSQL cluster. Tablespaces can be added at a later time by updating the `TablespaceMounts` entry, but they cannot be removed. Stores a map of information, with the key being the name of the tablespace, and the value being a Storage Specification, defined below. | +| tls | `create` | Defines the attributes for enabling TLS for a PostgreSQL cluster. See TLS Specification below. | +| tlsOnly | `create` | If set to true, requires client connections to use only TLS to connect to the PostgreSQL database. | +| tolerations | `create`,`update` | Any array of Kubernetes [Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/). Please refer to the [Kubernetes documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) for how to set this field. | +| user | `create` | The name of the PostgreSQL user that is created when the PostgreSQL cluster is first created. | +| userlabels | `create` | A set of key-value string pairs that are used as a sort of "catch-all" as well as a way to add custom labels to clusters. This will disappear at some point. | ##### Storage Specification @@ -778,13 +779,13 @@ attribute and how it works. | Attribute | Action | Description | |-----------|--------|-------------| -| AccessMode| `create` | The name of the Kubernetes Persistent Volume [Access Mode](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes) to use. | -| MatchLabels | `create` | Only used with `StorageType` of `create`, used to match a particular subset of provisioned Persistent Volumes. | -| Name | `create` | Only needed for `PrimaryStorage` in `pgclusters.crunchydata.com`.Used to identify the name of the PostgreSQL cluster. Should match `ClusterName`. | -| Size | `create` | The size of the [Persistent Volume Claim](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#persistentvolumeclaims) (PVC). Must use a Kubernetes resource value, e.g. `20Gi`. | -| StorageClass | `create` | The name of the Kubernetes [StorageClass](https://kubernetes.io/docs/concepts/storage/storage-classes/) to use. | -| StorageType | `create` | Set to `create` if storage is provisioned (e.g. using `hostpath`). Set to `dynamic` if using a dynamic storage provisioner, e.g. via a `StorageClass`. | -| SupplementalGroups | `create` | If provided, a comma-separated list of group IDs to use in case it is needed to interface with a particular storage system. Typically used with NFS or hostpath storage. | +| accessmode | `create` | The name of the Kubernetes Persistent Volume [Access Mode](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes) to use. | +| matchLabels | `create` | Only used with `StorageType` of `create`, used to match a particular subset of provisioned Persistent Volumes. | +| name | `create` | Only needed for `PrimaryStorage` in `pgclusters.crunchydata.com`.Used to identify the name of the PostgreSQL cluster. Should match `ClusterName`. | +| size | `create` | The size of the [Persistent Volume Claim](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#persistentvolumeclaims) (PVC). Must use a Kubernetes resource value, e.g. `20Gi`. | +| storageclass | `create` | The name of the Kubernetes [StorageClass](https://kubernetes.io/docs/concepts/storage/storage-classes/) to use. | +| storagetype | `create` | Set to `create` if storage is provisioned (e.g. using `hostpath`). Set to `dynamic` if using a dynamic storage provisioner, e.g. via a `StorageClass`. | +| supplementalgroups | `create` | If provided, a comma-separated list of group IDs to use in case it is needed to interface with a particular storage system. Typically used with NFS or hostpath storage. | ##### Node Affinity Specification @@ -815,9 +816,9 @@ documentation. | Attribute | Action | Description | |-----------|--------|-------------| -| Default | `create` | The default pod anti-affinity to use for all Pods managed in a given PostgreSQL cluster. | -| PgBackRest | `create` | If set to a value that differs from `Default`, specifies the pod anti-affinity to use for just the pgBackRest repository. | -| PgBouncer | `create` | If set to a value that differs from `Default`, specifies the pod anti-affinity to use for just the pgBouncer Pods. | +| default | `create` | The default pod anti-affinity to use for all Pods managed in a given PostgreSQL cluster. | +| pgBackRest | `create` | If set to a value that differs from `Default`, specifies the pod anti-affinity to use for just the pgBackRest repository. | +| pgBouncer | `create` | If set to a value that differs from `Default`, specifies the pod anti-affinity to use for just the pgBouncer Pods. | ##### PostgreSQL Data Source Specification @@ -828,8 +829,8 @@ spawning new PostgreSQL clusters. | Attribute | Action | Description | |-----------|--------|-------------| -| RestoreFrom | `create` | The name of a PostgreSQL cluster, active or former, that will be used for bootstrapping the data of a new PostgreSQL cluster. | -| RestoreOpts | `create` | Additional pgBackRest [restore options](https://pgbackrest.org/command.html#command-restore) that can be used as part of the bootstrapping operation, for example, point-in-time-recovery options. | +| restoreFrom | `create` | The name of a PostgreSQL cluster, active or former, that will be used for bootstrapping the data of a new PostgreSQL cluster. | +| restoreOpts | `create` | Additional pgBackRest [restore options](https://pgbackrest.org/command.html#command-restore) that can be used as part of the bootstrapping operation, for example, point-in-time-recovery options. | ##### TLS Specification @@ -839,9 +840,9 @@ should be structured, please see [Enabling TLS in a PostgreSQL Cluster]({{< relr | Attribute | Action | Description | |-----------|--------|-------------| -| CASecret | `create` | A reference to the name of a Kubernetes Secret that specifies a certificate authority for the PostgreSQL cluster to trust. | -| ReplicationTLSSecret | `create` | A reference to the name of a Kubernetes TLS Secret that contains a keypair for authenticating the replication user. Must be used with `CASecret` and `TLSSecret`. | -| TLSSecret | `create` | A reference to the name of a Kubernetes TLS Secret that contains a keypair that is used for the PostgreSQL instance to identify itself and perform TLS communications with PostgreSQL clients. Must be used with `CASecret`. | +| caSecret | `create` | A reference to the name of a Kubernetes Secret that specifies a certificate authority for the PostgreSQL cluster to trust. | +| replicationTLSSecret | `create` | A reference to the name of a Kubernetes TLS Secret that contains a keypair for authenticating the replication user. Must be used with `CASecret` and `TLSSecret`. | +| tlsSecret | `create` | A reference to the name of a Kubernetes TLS Secret that contains a keypair that is used for the PostgreSQL instance to identify itself and perform TLS communications with PostgreSQL clients. Must be used with `CASecret`. | ##### pgBouncer Specification @@ -852,11 +853,11 @@ a PostgreSQL cluster to help with failover scenarios too. | Attribute | Action | Description | |-----------|--------|-------------| -| Limits | `create`, `update` | Specify the container resource limits that the pgBouncer Pods should use. Follows the [Kubernetes definitions of resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | -| Replicas | `create`, `update` | The number of pgBouncer instances to deploy. Must be set to at least `1` to deploy pgBouncer. Setting to `0` removes an existing pgBouncer deployment for the PostgreSQL cluster. | -| Resources | `create`, `update` | Specify the container resource requests that the pgBouncer Pods should use. Follows the [Kubernetes definitions of resource requests](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | -| ServiceType | `create`, `update` | Sets the Kubernetes [Service](https://kubernetes.io/docs/concepts/services-networking/service/) type to use for the cluster. If not set, defaults to the `ServiceType` set for the PostgreSQL cluster. | -| TLSSecret | `create` | A reference to the name of a Kubernetes TLS Secret that contains a keypair that is used for the pgBouncer instance to identify itself and perform TLS communications with PostgreSQL clients. Must be used with the parent Spec `TLSSecret` and `CASecret`. | +| limits | `create`, `update` | Specify the container resource limits that the pgBouncer Pods should use. Follows the [Kubernetes definitions of resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | +| replicas | `create`, `update` | The number of pgBouncer instances to deploy. Must be set to at least `1` to deploy pgBouncer. Setting to `0` removes an existing pgBouncer deployment for the PostgreSQL cluster. | +| resources | `create`, `update` | Specify the container resource requests that the pgBouncer Pods should use. Follows the [Kubernetes definitions of resource requests](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | +| serviceType | `create`, `update` | Sets the Kubernetes [Service](https://kubernetes.io/docs/concepts/services-networking/service/) type to use for the cluster. If not set, defaults to the `ServiceType` set for the PostgreSQL cluster. | +| tlsSecret | `create` | A reference to the name of a Kubernetes TLS Secret that contains a keypair that is used for the pgBouncer instance to identify itself and perform TLS communications with PostgreSQL clients. Must be used with the parent Spec `TLSSecret` and `CASecret`. | ##### Annotations Specification @@ -874,10 +875,10 @@ different deployment groups. | Attribute | Action | Description | |-----------|--------|-------------| -| Backrest | `create`, `update` | Specify annotations that are only applied to the pgBackRest deployments | -| Global | `create`, `update` | Specify annotations that are applied to the PostgreSQL, pgBackRest, and pgBouncer deployments | -| PgBouncer | `create`, `update` | Specify annotations that are only applied to the pgBouncer deployments | -| Postgres | `create`, `update` | Specify annotations that are only applied to the PostgreSQL deployments | +| backrest | `create`, `update` | Specify annotations that are only applied to the pgBackRest deployments | +| global | `create`, `update` | Specify annotations that are applied to the PostgreSQL, pgBackRest, and pgBouncer deployments | +| pgBouncer | `create`, `update` | Specify annotations that are only applied to the pgBouncer deployments | +| postgres | `create`, `update` | Specify annotations that are only applied to the PostgreSQL deployments | ### `pgreplicas.crunchydata.com` @@ -889,10 +890,11 @@ cluster. All of the attributes only affect the replica when it is created. | Attribute | Action | Description | |-----------|--------|-------------| -| ClusterName | `create` | The name of the PostgreSQL cluster, e.g. `hippo`. This is used to group PostgreSQL instances (primary, replicas) together. | -| Name | `create` | The name of this PostgreSQL replica. It should be unique within a `ClusterName`. | -| Namespace | `create` | The Kubernetes Namespace that the PostgreSQL cluster is deployed in. | -| NodeAffinity | `create` | Sets the [node affinity rules]({{< relref "/architecture/high-availability/_index.md#node-affinity" >}}) for this PostgreSQL instance. Follows the [Kubernetes standard format for setting node affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity). | -| ReplicaStorage | `create` | A specification that gives information about the storage attributes for any replicas in the PostgreSQL cluster. For details, please see the `Storage Specification` section in the `pgclusters.crunchydata.com` description. This will likely be changed in the future based on the nature of the high-availability system, but presently it is still required that you set it. It is recommended you use similar settings to that of `PrimaryStorage`. | -| UserLabels | `create` | A set of key-value string pairs that are used as a sort of "catch-all" as well as a way to add custom labels to clusters. This will disappear at some point. | -| Tolerations | `create`,`update` | Any array of Kubernetes [Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/). Please refer to the [Kubernetes documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) for how to set this field. | +| clustername | `create` | The name of the PostgreSQL cluster, e.g. `hippo`. This is used to group PostgreSQL instances (primary, replicas) together. | +| name | `create` | The name of this PostgreSQL replica. It should be unique within a `ClusterName`. | +| namespace | `create` | The Kubernetes Namespace that the PostgreSQL cluster is deployed in. | +| nodeAffinity | `create` | Sets the [node affinity rules]({{< relref "/architecture/high-availability/_index.md#node-affinity" >}}) for this PostgreSQL instance. Follows the [Kubernetes standard format for setting node affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity). | +| replicastorage | `create` | A specification that gives information about the storage attributes for any replicas in the PostgreSQL cluster. For details, please see the `Storage Specification` section in the `pgclusters.crunchydata.com` description. This will likely be changed in the future based on the nature of the high-availability system, but presently it is still required that you set it. It is recommended you use similar settings to that of `PrimaryStorage`. | +| serviceType | `create`, `update` | Sets the Kubernetes [Service](https://kubernetes.io/docs/concepts/services-networking/service/) type to use for this particular instance. If not set, defaults to the value in the related `pgclusters.crunchydata.com` custom resource. | +| userlabels | `create` | A set of key-value string pairs that are used as a sort of "catch-all" as well as a way to add custom labels to clusters. This will disappear at some point. | +| tolerations | `create`,`update` | Any array of Kubernetes [Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/). Please refer to the [Kubernetes documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) for how to set this field. | From b00bab88ddb97ed225b3ac660e237984c8e1a972 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 3 Jan 2021 10:51:19 -0500 Subject: [PATCH 109/276] Add support for toleration updates using pgo client This commit adds the `--toleration` flag to the `pgo update cluster` command and supports to adding and removing of tolerations to an existing PostgreSQL cluster. A toleration can be added in similar fashion to the --toleration flag when running `pgo create cluster` or `pgo scale`. A toleration can be removed by appending a `-` to the end of the toleration flag value, e.g. pgo update cluster hippo --toleration=zone=east:NoSchedule- --- cmd/pgo/cmd/cluster.go | 22 +++++++++++++-- cmd/pgo/cmd/create.go | 4 +++ cmd/pgo/cmd/scale.go | 2 +- cmd/pgo/cmd/update.go | 6 ++++ .../architecture/high-availability/_index.md | 23 +++++++++++---- .../reference/pgo_update_cluster.md | 7 ++++- docs/content/tutorial/customize-cluster.md | 8 ++++++ .../apiserver/clusterservice/clusterimpl.go | 28 +++++++++++++++++++ pkg/apiservermsgs/clustermsgs.go | 6 ++++ 9 files changed, 96 insertions(+), 10 deletions(-) diff --git a/cmd/pgo/cmd/cluster.go b/cmd/pgo/cmd/cluster.go index da97cef37a..e66a28fc32 100644 --- a/cmd/pgo/cmd/cluster.go +++ b/cmd/pgo/cmd/cluster.go @@ -337,7 +337,7 @@ func createCluster(args []string, ns string, createClusterCmd *cobra.Command) { r.Annotations = getClusterAnnotations(Annotations, AnnotationsPostgres, AnnotationsBackrest, AnnotationsPgBouncer) // set any tolerations - r.Tolerations = getClusterTolerations(Tolerations) + r.Tolerations = getClusterTolerations(Tolerations, false) // only set SyncReplication in the request if actually provided via the CLI if createClusterCmd.Flag("sync-replication").Changed { @@ -557,7 +557,11 @@ func getTablespaces(tablespaceParams []string) []msgs.ClusterTablespaceDetail { // // Exists - key:Effect // Equals - key=value:Effect -func getClusterTolerations(tolerationList []string) []v1.Toleration { +// +// If the remove flag is set to true, check for a trailing "-" at the end of +// each item, as this will be a remove list. Otherwise, only consider +// tolerations that are not being removed +func getClusterTolerations(tolerationList []string, remove bool) []v1.Toleration { tolerations := make([]v1.Toleration, 0) // if no tolerations, exit early @@ -577,7 +581,17 @@ func getClusterTolerations(tolerationList []string) []v1.Toleration { } // for ease of reading - rule, effect := ruleEffect[0], v1.TaintEffect(ruleEffect[1]) + rule, effectStr := ruleEffect[0], ruleEffect[1] + + // determine if the effect is for removal or not, as we will continue the + // loop based on that + if (remove && !strings.HasSuffix(effectStr, "-")) || (!remove && strings.HasSuffix(effectStr, "-")) { + continue + } + + // no matter what we can trim any trailing "-" off of the string, and cast + // it as a TaintEffect + effect := v1.TaintEffect(strings.TrimSuffix(effectStr, "-")) // see if the effect is a valid effect if !isValidTaintEffect(effect) { @@ -687,6 +701,8 @@ func updateCluster(args []string, ns string) { // set any annotations r.Annotations = getClusterAnnotations(Annotations, AnnotationsPostgres, AnnotationsBackrest, AnnotationsPgBouncer) + r.Tolerations = getClusterTolerations(Tolerations, false) + r.TolerationsDelete = getClusterTolerations(Tolerations, true) // check to see if EnableStandby or DisableStandby is set. If so, // set a value for Standby diff --git a/cmd/pgo/cmd/create.go b/cmd/pgo/cmd/create.go index f301111797..fa6e71e32b 100644 --- a/cmd/pgo/cmd/create.go +++ b/cmd/pgo/cmd/create.go @@ -156,6 +156,10 @@ var ( // Example: // // zone=east:NoSchedule,highspeed:NoSchedule +// +// A toleration can be removed by adding a "-" to the end, e.g.: +// +// zone=east:NoSchedule- var Tolerations []string var CreateCmd = &cobra.Command{ diff --git a/cmd/pgo/cmd/scale.go b/cmd/pgo/cmd/scale.go index a44fa959f2..833b7de3af 100644 --- a/cmd/pgo/cmd/scale.go +++ b/cmd/pgo/cmd/scale.go @@ -82,7 +82,7 @@ func scaleCluster(args []string, ns string) { ReplicaCount: ReplicaCount, ServiceType: v1.ServiceType(ServiceType), StorageConfig: StorageConfig, - Tolerations: getClusterTolerations(Tolerations), + Tolerations: getClusterTolerations(Tolerations, false), } response, err := api.ScaleCluster(httpclient, &SessionCredentials, request) diff --git a/cmd/pgo/cmd/update.go b/cmd/pgo/cmd/update.go index 89d88fe45b..d09c506c7b 100644 --- a/cmd/pgo/cmd/update.go +++ b/cmd/pgo/cmd/update.go @@ -144,6 +144,12 @@ func init() { "Follows the Kubernetes quantity format.\n\n"+ "For example, to create a tablespace with the NFS storage configuration with a PVC of size 10GiB:\n\n"+ "--tablespace=name=ts1:storageconfig=nfsstorage:pvcsize=10Gi") + UpdateClusterCmd.Flags().StringSliceVar(&Tolerations, "toleration", []string{}, + "Set Pod tolerations for each PostgreSQL instance in a cluster.\n"+ + "The general format is \"key=value:Effect\"\n"+ + "For example, to add an Exists and an Equals toleration: \"--toleration=ssd:NoSchedule,zone=east:NoSchedule\"\n"+ + "A toleration can be removed by adding a \"-\" to the end, for example:\n"+ + "--toleration=ssd:NoSchedule-") UpdatePgBouncerCmd.Flags().StringVar(&PgBouncerCPURequest, "cpu", "", "Set the number of millicores to request for CPU "+ "for pgBouncer.") UpdatePgBouncerCmd.Flags().StringVar(&PgBouncerCPULimit, "cpu-limit", "", "Set the number of millicores to limit for CPU "+ diff --git a/docs/content/architecture/high-availability/_index.md b/docs/content/architecture/high-availability/_index.md index f267d306f0..073df5e599 100644 --- a/docs/content/architecture/high-availability/_index.md +++ b/docs/content/architecture/high-availability/_index.md @@ -345,11 +345,24 @@ following command: pgo scale hippo --toleration=zone=west:NoSchedule ``` -Tolerations can be updated on an existing cluster. To do so, you will need to -modify the `pgclusters.crunchydata.com` and `pgreplicas.crunchydata.com` custom -resources directly, e.g. via the `kubectl edit` command. Once the updates are -applied, the PostgreSQL Operator will roll out the changes to the appropriate -instances. +Tolerations can be updated on an existing cluster. You can do this by either +modifying the `pgclusters.crunchydata.com` and `pgreplicas.crunchydata.com` +custom resources directly, e.g. via the `kubectl edit` command, or with the +[`pgo update cluster`]({{ relref "pgo-client/reference/pgo_update_cluster.md" }}) +command. Using the `pgo update cluster` command, a toleration can be removed by +adding a `-` at the end of the toleration effect. + +For example, to add a toleration of `zone=west:NoSchedule` and remove the +toleration of `zone=east:NoSchedule`, you could run the following command: + +``` +pgo update cluster hippo \ + --toleration=zone=west:NoSchedule \ + --toleration=zone-east:NoSchedule- +``` + +Once the updates are applied, the PostgreSQL Operator will roll out the changes +to the appropriate instances. ## Rolling Updates diff --git a/docs/content/pgo-client/reference/pgo_update_cluster.md b/docs/content/pgo-client/reference/pgo_update_cluster.md index 70689cdfea..3596be5282 100644 --- a/docs/content/pgo-client/reference/pgo_update_cluster.md +++ b/docs/content/pgo-client/reference/pgo_update_cluster.md @@ -71,6 +71,11 @@ pgo update cluster [flags] For example, to create a tablespace with the NFS storage configuration with a PVC of size 10GiB: --tablespace=name=ts1:storageconfig=nfsstorage:pvcsize=10Gi + --toleration strings Set Pod tolerations for each PostgreSQL instance in a cluster. + The general format is "key=value:Effect" + For example, to add an Exists and an Equals toleration: "--toleration=ssd:NoSchedule,zone=east:NoSchedule" + A toleration can be removed by adding a "-" to the end, for example: + --toleration=ssd:NoSchedule- ``` ### Options inherited from parent commands @@ -90,4 +95,4 @@ pgo update cluster [flags] * [pgo update](/pgo-client/reference/pgo_update/) - Update a pgouser, pgorole, or cluster -###### Auto generated by spf13/cobra on 2-Jan-2021 +###### Auto generated by spf13/cobra on 3-Jan-2021 diff --git a/docs/content/tutorial/customize-cluster.md b/docs/content/tutorial/customize-cluster.md index 3cd1f6d374..8d5f4f941d 100644 --- a/docs/content/tutorial/customize-cluster.md +++ b/docs/content/tutorial/customize-cluster.md @@ -158,6 +158,14 @@ pgo create cluster hippo \ --toleration=zone=east:NoSchedule ``` +Tolerations can be updated on an existing cluster using the [`pgo update cluster`]({{ relref "pgo-client/reference/pgo_update_cluster.md" }}) command. For example, to add a toleration of `zone=west:NoSchedule` and remove the toleration of `zone=east:NoSchedule`, you could run the following command: + +``` +pgo update cluster hippo \ + --toleration=zone=west:NoSchedule \ + --toleration=zone-east:NoSchedule- +``` + You can also add or edit tolerations directly on the `pgclusters.crunchydata.com` custom resource and the PostgreSQL Operator will roll out the changes to the appropriate instances. ## Customize PostgreSQL Configuration diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index 22f10a7f14..12b8604391 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" "io/ioutil" + "reflect" "strconv" "strings" "time" @@ -2072,6 +2073,33 @@ func UpdateCluster(request *msgs.UpdateClusterRequest) msgs.UpdateClusterRespons cluster.Spec.TablespaceMounts[tablespace.Name] = storageSpec } + // Handle any tolerations. This is fun. So we will have to go through both + // the toleration addition list as well as the toleration subtraction list. + // + // First, we will remove any tolerations that are slated for removal + if len(request.TolerationsDelete) > 0 { + tolerations := make([]v1.Toleration, 0) + + for _, toleration := range cluster.Spec.Tolerations { + delete := false + + for _, tolerationDelete := range request.TolerationsDelete { + delete = delete || (reflect.DeepEqual(toleration, tolerationDelete)) + } + + // if delete does not match, then we can include this toleration in any + // updates + if !delete { + tolerations = append(tolerations, toleration) + } + } + + cluster.Spec.Tolerations = tolerations + } + + // now, add any new tolerations to the spec + cluster.Spec.Tolerations = append(cluster.Spec.Tolerations, request.Tolerations...) + if _, err := apiserver.Clientset.CrunchydataV1().Pgclusters(request.Namespace).Update(ctx, &cluster, metav1.UpdateOptions{}); err != nil { response.Status.Code = msgs.Error response.Status.Msg = err.Error() diff --git a/pkg/apiservermsgs/clustermsgs.go b/pkg/apiservermsgs/clustermsgs.go index d4287fd3cd..d6cbf91fd2 100644 --- a/pkg/apiservermsgs/clustermsgs.go +++ b/pkg/apiservermsgs/clustermsgs.go @@ -474,6 +474,12 @@ type UpdateClusterRequest struct { Startup bool Shutdown bool Tablespaces []ClusterTablespaceDetail + // Tolerations allows for the adding of Pod tolerations on a PostgreSQL + // cluster. + Tolerations []v1.Toleration `json:"tolerations"` + // TolerationsDelete allows for the removal of Pod tolerations on a + // PostgreSQL cluster + TolerationsDelete []v1.Toleration `json:"tolerationsDelete"` } // UpdateClusterResponse ... From e2af460ae7555090f26a330652c165c0cb4c17f2 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 3 Jan 2021 13:56:54 -0500 Subject: [PATCH 110/276] Change default use for executing scheduled policy Previously this was the user meant for replication. Now this defaults to the superuser. --- internal/apiserver/scheduleservice/scheduleimpl.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/apiserver/scheduleservice/scheduleimpl.go b/internal/apiserver/scheduleservice/scheduleimpl.go index 04c027f391..07efb121a8 100644 --- a/internal/apiserver/scheduleservice/scheduleimpl.go +++ b/internal/apiserver/scheduleservice/scheduleimpl.go @@ -77,8 +77,9 @@ func (s scheduleRequest) createPolicySchedule(cluster *crv1.Pgcluster, ns string } if s.Request.Secret == "" { - s.Request.Secret = crv1.UserSecretName(cluster, crv1.PGUserReplication) + s.Request.Secret = crv1.UserSecretName(cluster, crv1.PGUserSuperuser) } + schedule := &PgScheduleSpec{ Name: name, Cluster: cluster.Name, From cfb35ff15ab123d91ea29a6e13febab8d149fef7 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 3 Jan 2021 11:15:28 -0500 Subject: [PATCH 111/276] Modify internal call for reload command This moves away from making a direct call to the REST API and leverages the command directly. --- .../apiserver/reloadservice/reloadimpl.go | 30 ------------------- internal/patroni/patroni.go | 22 +++++++------- pkg/events/eventtype.go | 15 ---------- 3 files changed, 11 insertions(+), 56 deletions(-) diff --git a/internal/apiserver/reloadservice/reloadimpl.go b/internal/apiserver/reloadservice/reloadimpl.go index cb6978e5f5..465b6a9e26 100644 --- a/internal/apiserver/reloadservice/reloadimpl.go +++ b/internal/apiserver/reloadservice/reloadimpl.go @@ -19,13 +19,11 @@ import ( "context" "fmt" "strings" - "time" "github.com/crunchydata/postgres-operator/internal/apiserver" "github.com/crunchydata/postgres-operator/internal/config" "github.com/crunchydata/postgres-operator/internal/patroni" msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" - "github.com/crunchydata/postgres-operator/pkg/events" log "github.com/sirupsen/logrus" kerrors "k8s.io/apimachinery/pkg/api/errors" @@ -99,11 +97,6 @@ func Reload(request *msgs.ReloadRequest, ns, username string) msgs.ReloadRespons } resp.Results = append(resp.Results, fmt.Sprintf("reload performed on %s", clusterName)) - - if err := publishReloadClusterEvent(cluster.GetName(), ns, username); err != nil { - log.Error(err.Error()) - errorMsgs = append(errorMsgs, err.Error()) - } } if len(errorMsgs) > 0 { @@ -113,26 +106,3 @@ func Reload(request *msgs.ReloadRequest, ns, username string) msgs.ReloadRespons return resp } - -// publishReloadClusterEvent publishes an event when a cluster is reloaded -func publishReloadClusterEvent(clusterName, username, namespace string) error { - topics := make([]string, 1) - topics[0] = events.EventTopicCluster - - f := events.EventReloadClusterFormat{ - EventHeader: events.EventHeader{ - Namespace: namespace, - Username: username, - Topic: topics, - Timestamp: time.Now(), - EventType: events.EventReloadCluster, - }, - Clustername: clusterName, - } - - if err := events.Publish(f); err != nil { - return err - } - - return nil -} diff --git a/internal/patroni/patroni.go b/internal/patroni/patroni.go index 3b20703c0e..f4384a74e8 100644 --- a/internal/patroni/patroni.go +++ b/internal/patroni/patroni.go @@ -35,12 +35,10 @@ import ( const dbContainerName = "database" var ( - // reloadCMD is the command for reloading a specific PG instance (primary or replica) within a - // PG cluster - reloadCMD = []string{ - "/bin/bash", "-c", - fmt.Sprintf("curl -X POST --silent http://127.0.0.1:%s/reload", config.DEFAULT_PATRONI_PORT), - } + // reloadCMD is the command for reloading a specific PG instance (primary or + // replica) within a Postgres cluster. It requires a cluster and instance name + // to be appended to it + reloadCMD = []string{"patronictl", "reload", "--force"} // restartCMD is the command for restart a specific PG database (primary or replica) within a // PG cluster restartCMD = []string{ @@ -195,17 +193,19 @@ func (p *patroniClient) RestartInstances(instances ...string) ([]RestartResult, // reload performs a Patroni reload (which includes a PG reload) on a specific instance (primary or // replica) within a PG cluster func (p *patroniClient) reload(podName string) error { - stdout, stderr, err := kubeapi.ExecToPodThroughAPI(p.restConfig, p.kubeclientset, reloadCMD, - dbContainerName, podName, p.namespace, nil) + cmd := reloadCMD + cmd = append(cmd, p.clusterName, podName) + + stdout, stderr, err := kubeapi.ExecToPodThroughAPI(p.restConfig, p.kubeclientset, + cmd, dbContainerName, podName, p.namespace, nil) + if err != nil { - return err - } else if stderr != "" { return fmt.Errorf(stderr) } log.Debugf("Successfully reloaded PG on pod %s: %s", podName, stdout) - return err + return nil } // restart performs a Patroni restart on a specific instance (primary or replica) within a PG diff --git a/pkg/events/eventtype.go b/pkg/events/eventtype.go index b93159e77e..8a2031d08b 100644 --- a/pkg/events/eventtype.go +++ b/pkg/events/eventtype.go @@ -104,21 +104,6 @@ type EventInterface interface { String() string } -//-------- -type EventReloadClusterFormat struct { - EventHeader `json:"eventheader"` - Clustername string `json:"clustername"` -} - -func (p EventReloadClusterFormat) GetHeader() EventHeader { - return p.EventHeader -} - -func (lvl EventReloadClusterFormat) String() string { - msg := fmt.Sprintf("Event %s - (reload) name %s", lvl.EventHeader, lvl.Clustername) - return msg -} - //---------------------------- type EventCreateClusterFailureFormat struct { EventHeader `json:"eventheader"` From 955c6e841509413d9bc68d1dab97d56d4078bd12 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 3 Jan 2021 11:21:42 -0500 Subject: [PATCH 112/276] Modify internal call for restart command This moves away from making a direct call to the API and instead leverages the command to perform a restart. --- internal/patroni/patroni.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/internal/patroni/patroni.go b/internal/patroni/patroni.go index f4384a74e8..70651dcace 100644 --- a/internal/patroni/patroni.go +++ b/internal/patroni/patroni.go @@ -39,12 +39,10 @@ var ( // replica) within a Postgres cluster. It requires a cluster and instance name // to be appended to it reloadCMD = []string{"patronictl", "reload", "--force"} - // restartCMD is the command for restart a specific PG database (primary or replica) within a - // PG cluster - restartCMD = []string{ - "/bin/bash", "-c", - fmt.Sprintf("curl -X POST --silent http://127.0.0.1:%s/restart", config.DEFAULT_PATRONI_PORT), - } + // restartCMD is the command for restart a specific PG database (primary or + // replica) within a Postgres cluster. It requires a cluster and instance name + // to be appended to it. + restartCMD = []string{"patronictl", "restart", "--force"} // ErrInstanceNotFound is the error thrown when a target instance cannot be found in the cluster ErrInstanceNotFound = errors.New("The instance does not exist in the cluster") @@ -211,7 +209,10 @@ func (p *patroniClient) reload(podName string) error { // restart performs a Patroni restart on a specific instance (primary or replica) within a PG // cluster. func (p *patroniClient) restart(podName string) error { - stdout, stderr, err := kubeapi.ExecToPodThroughAPI(p.restConfig, p.kubeclientset, restartCMD, + cmd := restartCMD + cmd = append(cmd, p.clusterName, podName) + + stdout, stderr, err := kubeapi.ExecToPodThroughAPI(p.restConfig, p.kubeclientset, cmd, dbContainerName, podName, p.namespace, nil) if err != nil { return err From f9eafa93a5013c76980f09458420787b7fcd88d1 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Fri, 1 Jan 2021 16:28:39 -0500 Subject: [PATCH 113/276] Move `pgo failover` to use updated switchover plumbing The manual failover/switchover command, `pgo failover`, now uses the updated "switchover" plumbing that was introduced as part of the rolling update changes. This ensures a unified experience when performing an action that involves a failover. Additionally, `pgo failover` now occurs inline: it does not create a pgtask custom resource. This is due to both being a much simpler process and the transactional nature of an immediate failover. `pgo failover` can now also be executed with a --target flag. This was actually always supported, but unavailable based upon a restriction in the `pgo` client. When the `--target` flag is not used, the PostgreSQL Operator will choose the best candidate for failing over. --- cmd/pgo/cmd/failover.go | 21 +- docs/content/pgo-client/common-tasks.md | 27 +- .../pgo-client/reference/pgo_failover.md | 11 +- docs/content/tutorial/high-availability.md | 23 +- .../apiserver/failoverservice/failoverimpl.go | 71 ++---- internal/config/labels.go | 9 +- .../controller/pgtask/pgtaskcontroller.go | 28 --- internal/operator/cluster/failover.go | 134 +++------- internal/operator/cluster/failoverlogic.go | 234 ------------------ internal/operator/cluster/rolling.go | 49 +--- internal/operator/switchover.go | 156 ++++++++++++ internal/operator/switchover_test.go | 45 ++++ internal/util/failover.go | 38 --- pkg/apis/crunchydata.com/v1/task.go | 2 - pkg/apiservermsgs/failovermsgs.go | 3 +- pkg/events/eventtype.go | 34 --- 16 files changed, 326 insertions(+), 559 deletions(-) delete mode 100644 internal/operator/cluster/failoverlogic.go create mode 100644 internal/operator/switchover.go create mode 100644 internal/operator/switchover_test.go diff --git a/cmd/pgo/cmd/failover.go b/cmd/pgo/cmd/failover.go index bfbd3b0848..b67cd1d2d1 100644 --- a/cmd/pgo/cmd/failover.go +++ b/cmd/pgo/cmd/failover.go @@ -32,7 +32,12 @@ var failoverCmd = &cobra.Command{ Short: "Performs a manual failover", Long: `Performs a manual failover. For example: - pgo failover mycluster`, + # have the operator select the best target candidate + pgo failover hippo + # get a list of target candidates + pgo failover hippo --query + # failover to a specific target candidate + pgo failover hippo --target=hippo-abcd`, Run: func(cmd *cobra.Command, args []string) { if Namespace == "" { Namespace = PGONamespace @@ -44,10 +49,6 @@ var failoverCmd = &cobra.Command{ if Query { queryFailover(args, Namespace) } else if util.AskForConfirmation(NoPrompt, "") { - if Target == "" { - fmt.Println(`Error: The --target flag is required for failover.`) - return - } createFailover(args, Namespace) } else { fmt.Println("Aborting...") @@ -80,14 +81,12 @@ func createFailover(args []string, ns string) { os.Exit(2) } - if response.Status.Code == msgs.Ok { - for k := range response.Results { - fmt.Println(response.Results[k]) - } - } else { + if response.Status.Code != msgs.Ok { fmt.Println("Error: " + response.Status.Msg) - os.Exit(2) + os.Exit(1) } + + fmt.Println(response.Results) } // queryFailover is a helper function to return the user information about the diff --git a/docs/content/pgo-client/common-tasks.md b/docs/content/pgo-client/common-tasks.md index d5ae6b8b33..ae0129a86d 100644 --- a/docs/content/pgo-client/common-tasks.md +++ b/docs/content/pgo-client/common-tasks.md @@ -816,13 +816,26 @@ pgo failover --query hacluster The PostgreSQL Operator is set up with an automated failover system based on distributed consensus, but there may be times where you wish to have your -cluster manually failover. If you wish to have your cluster manually failover, -first, query your cluster to determine which failover targets are available. -The query command also provides information that may help your decision, such as -replication lag: +cluster manually failover. There are two ways to issue a manual failover to +your PostgreSQL cluster: + +1. Allow for the PostgreSQL Operator to select the best replica candidate to +failover to +2. Select your own replica candidate to failover to. + +To have the PostgreSQL Operator select the best replica candidate for failover, +all you need to do is execute the following command: + +``` +pgo failover hacluster +``` + +If you wish to have your cluster manually failover, you must first query your +cluster to determine which failover targets are available. The query command +also provides information that may help your decision, such as replication lag: ```shell -pgo failover --query hacluster +pgo failover hacluster --query ``` Once you have selected the replica that is best for your to failover to, you can @@ -833,7 +846,9 @@ pgo failover hacluster --target=hacluster-abcd ``` where `hacluster-abcd` is the name of the PostgreSQL instance that you want to -promote to become the new primary +promote to become the new primary. + +Both methods perform the failover immediately upon execution. #### Destroying a Replica diff --git a/docs/content/pgo-client/reference/pgo_failover.md b/docs/content/pgo-client/reference/pgo_failover.md index d60cefd417..ea65e16f04 100644 --- a/docs/content/pgo-client/reference/pgo_failover.md +++ b/docs/content/pgo-client/reference/pgo_failover.md @@ -9,7 +9,12 @@ Performs a manual failover Performs a manual failover. For example: - pgo failover mycluster + # have the operator select the best target candidate + pgo failover hippo + # get a list of target candidates + pgo failover hippo --query + # failover to a specific target candidate + pgo failover hippo --target=hippo-abcd ``` pgo failover [flags] @@ -27,7 +32,7 @@ pgo failover [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -41,4 +46,4 @@ pgo failover [flags] * [pgo](/pgo-client/reference/pgo/) - The pgo command line interface. -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 1-Jan-2021 diff --git a/docs/content/tutorial/high-availability.md b/docs/content/tutorial/high-availability.md index b85a8469cc..2d277d382c 100644 --- a/docs/content/tutorial/high-availability.md +++ b/docs/content/tutorial/high-availability.md @@ -62,7 +62,28 @@ pgo scaledown hippo --target=hippo-ojnd ## Manual Failover -Each PostgreSQL cluster will manage its own availability. If you wish to manually fail over, you will need to use the [`pgo failover`]({{< relref "pgo-client/reference/pgo_failover.md">}}) command. First, determine which instance you want to fail over to: +Each PostgreSQL cluster will manage its own availability. If you wish to manually fail over, you will need to use the [`pgo failover`]({{< relref "pgo-client/reference/pgo_failover.md">}}) command. + +There are two ways to issue a manual failover to your PostgreSQL cluster: + +1. Allow for the PostgreSQL Operator to select the best replica candidate for failover. +2. Select your own replica candidate for failover. + +Both methods are detailed below. + +### Manual Failover - PostgreSQL Operator Candidate Selection + +To have the PostgreSQL Operator select the best replica candidate for failover, all you need to do is execute the following command: + +``` +pgo failover hippo +``` + +The PostgreSQL Operator will determine which is the best replica candidate to fail over to, and take into account factors such as replication lag and current timeline. + +### Manual Failover - Manual Selection + +If you wish to have your cluster manually failover, you must first query your determine which instance you want to fail over to. You can do so with the following command: ``` pgo failover hippo --query diff --git a/internal/apiserver/failoverservice/failoverimpl.go b/internal/apiserver/failoverservice/failoverimpl.go index ca25062303..983496b7ed 100644 --- a/internal/apiserver/failoverservice/failoverimpl.go +++ b/internal/apiserver/failoverservice/failoverimpl.go @@ -18,29 +18,32 @@ limitations under the License. import ( "context" "errors" + "fmt" "github.com/crunchydata/postgres-operator/internal/apiserver" "github.com/crunchydata/postgres-operator/internal/config" + "github.com/crunchydata/postgres-operator/internal/operator" "github.com/crunchydata/postgres-operator/internal/util" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" log "github.com/sirupsen/logrus" - v1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// CreateFailover ... +// CreateFailover is the API endpoint for triggering a manual failover of a +// cluster. It performs this function inline, i.e. it does not trigger any +// asynchronous methods. +// // pgo failover mycluster -// pgo failover all -// pgo failover --selector=name=mycluster func CreateFailover(request *msgs.CreateFailoverRequest, ns, pgouser string) msgs.CreateFailoverResponse { - ctx := context.TODO() + log.Debugf("create failover called for %s", request.ClusterName) - var err error - resp := msgs.CreateFailoverResponse{} - resp.Status.Code = msgs.Ok - resp.Status.Msg = "" - resp.Results = make([]string, 0) + resp := msgs.CreateFailoverResponse{ + Results: "", + Status: msgs.Status{ + Code: msgs.Ok, + }, + } cluster, err := validateClusterName(request.ClusterName, ns) if err != nil { @@ -58,49 +61,21 @@ func CreateFailover(request *msgs.CreateFailoverRequest, ns, pgouser string) msg } if request.Target != "" { - _, err = isValidFailoverTarget(request.Target, request.ClusterName, ns) - if err != nil { + if err := isValidFailoverTarget(request.Target, request.ClusterName, ns); err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() return resp } } - log.Debugf("create failover called for %s", request.ClusterName) - - // Create a pgtask - spec := crv1.PgtaskSpec{} - spec.Namespace = ns - spec.Name = request.ClusterName + "-" + config.LABEL_FAILOVER - - // previous failovers will leave a pgtask so remove it first - _ = apiserver.Clientset.CrunchydataV1().Pgtasks(ns).Delete(ctx, spec.Name, metav1.DeleteOptions{}) - - spec.TaskType = crv1.PgtaskFailover - spec.Parameters = make(map[string]string) - spec.Parameters[request.ClusterName] = request.ClusterName - - labels := make(map[string]string) - labels["target"] = request.Target - labels[config.LABEL_PG_CLUSTER] = request.ClusterName - labels[config.LABEL_PGOUSER] = pgouser - - newInstance := &crv1.Pgtask{ - ObjectMeta: metav1.ObjectMeta{ - Name: spec.Name, - Labels: labels, - }, - Spec: spec, - } - - _, err = apiserver.Clientset.CrunchydataV1().Pgtasks(ns).Create(ctx, newInstance, metav1.CreateOptions{}) - if err != nil { + // perform the switchover + if err := operator.Switchover(apiserver.Clientset, apiserver.RESTConfig, cluster, request.Target); err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() return resp } - resp.Results = append(resp.Results, "created Pgtask (failover) for cluster "+request.ClusterName) + resp.Results = "failover success for cluster " + cluster.Name return resp } @@ -186,7 +161,7 @@ func validateClusterName(clusterName, ns string) (*crv1.Pgcluster, error) { // specified, and then ensuring the PG pod created by the deployment is not the current primary. // If the deployment is not found, or if the pod is the current primary, an error will be returned. // Otherwise the deployment is returned. -func isValidFailoverTarget(deployName, clusterName, ns string) (*v1.Deployment, error) { +func isValidFailoverTarget(deployName, clusterName, ns string) error { ctx := context.TODO() // Using the following label selector, ensure the deployment specified using deployName exists in the @@ -198,11 +173,11 @@ func isValidFailoverTarget(deployName, clusterName, ns string) (*v1.Deployment, List(ctx, metav1.ListOptions{LabelSelector: selector}) if err != nil { log.Error(err) - return nil, err + return err } else if len(deployments.Items) == 0 { - return nil, errors.New("no target found named " + deployName) + return fmt.Errorf("no target found named %s", deployName) } else if len(deployments.Items) > 1 { - return nil, errors.New("more than one target found named " + deployName) + return fmt.Errorf("more than one target found named %s", deployName) } // Using the following label selector, determine if the target specified is the current @@ -212,8 +187,8 @@ func isValidFailoverTarget(deployName, clusterName, ns string) (*v1.Deployment, "," + config.LABEL_PGHA_ROLE + "=" + config.LABEL_PGHA_ROLE_PRIMARY pods, _ := apiserver.Clientset.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{LabelSelector: selector}) if len(pods.Items) > 0 { - return nil, errors.New("The primary database cannot be selected as a failover target") + return fmt.Errorf("The primary database cannot be selected as a failover target") } - return &deployments.Items[0], nil + return nil } diff --git a/internal/config/labels.go b/internal/config/labels.go index 9308d17f91..327eb74183 100644 --- a/internal/config/labels.go +++ b/internal/config/labels.go @@ -27,19 +27,14 @@ const ( const LABEL_PGTASK = "pg-task" -const ( - LABEL_FAILOVER = "failover" - LABEL_RESTART = "restart" -) +const LABEL_RESTART = "restart" const ( - LABEL_TARGET = "target" LABEL_RMDATA = "pgrmdata" ) const ( LABEL_PGPOLICY = "pgpolicy" - LABEL_INGEST = "ingest" LABEL_PGREMOVE = "pgremove" LABEL_PVCNAME = "pvcname" LABEL_EXPORTER = "crunchy-postgres-exporter" @@ -179,8 +174,6 @@ const ( LABEL_PGO_UPDATED_BY = "pgo-updated-by" ) -const LABEL_FAILOVER_STARTED = "failover-started" - const GLOBAL_CUSTOM_CONFIGMAP = "pgo-custom-pg-config" const ( diff --git a/internal/controller/pgtask/pgtaskcontroller.go b/internal/controller/pgtask/pgtaskcontroller.go index d26d7231d4..d52ccf6dd0 100644 --- a/internal/controller/pgtask/pgtaskcontroller.go +++ b/internal/controller/pgtask/pgtaskcontroller.go @@ -122,13 +122,6 @@ func (c *Controller) processNextItem() bool { case crv1.PgtaskUpgrade: log.Debug("upgrade task added") clusteroperator.AddUpgrade(c.Client, tmpTask, keyNamespace) - case crv1.PgtaskFailover: - log.Debug("failover task added") - if !dupeFailover(c.Client, tmpTask, keyNamespace) { - clusteroperator.FailoverBase(keyNamespace, c.Client, tmpTask, c.Client.Config) - } else { - log.Debugf("skipping duplicate onAdd failover task %s/%s", keyNamespace, keyResourceName) - } case crv1.PgtaskRollingUpdate: log.Debug("rolling update task added") // first, attempt to get the pgcluster object @@ -164,9 +157,6 @@ func (c *Controller) processNextItem() bool { case crv1.PgtaskpgRestore: log.Debug("pgDump restore task added") pgdumpoperator.Restore(keyNamespace, c.Client, tmpTask) - - case crv1.PgtaskAutoFailover: - log.Debugf("autofailover task added %s", keyResourceName) case crv1.PgtaskWorkflow: log.Debugf("workflow task added [%s] ID [%s]", keyResourceName, tmpTask.Spec.Parameters[crv1.PgtaskWorkflowID]) @@ -217,24 +207,6 @@ func (c *Controller) AddPGTaskEventHandler() { log.Debugf("pgtask Controller: added event handler to informer") } -// de-dupe logic for a failover, if the failover started -// parameter is set, it means a failover has already been -// started on this -func dupeFailover(clientset pgo.Interface, task *crv1.Pgtask, ns string) bool { - ctx := context.TODO() - tmp, err := clientset.CrunchydataV1().Pgtasks(ns).Get(ctx, task.Spec.Name, metav1.GetOptions{}) - if err != nil { - // a big time error if this occurs - return false - } - - if tmp.Spec.Parameters[config.LABEL_FAILOVER_STARTED] == "" { - return false - } - - return true -} - // de-dupe logic for a delete data, if the delete data job started // parameter is set, it means a delete data job has already been // started on this diff --git a/internal/operator/cluster/failover.go b/internal/operator/cluster/failover.go index d1ff4fb033..67d43e3c05 100644 --- a/internal/operator/cluster/failover.go +++ b/internal/operator/cluster/failover.go @@ -20,121 +20,63 @@ package cluster import ( "context" - "encoding/json" - "time" + "fmt" "github.com/crunchydata/postgres-operator/internal/config" "github.com/crunchydata/postgres-operator/internal/kubeapi" - crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" - "github.com/crunchydata/postgres-operator/pkg/events" - pgo "github.com/crunchydata/postgres-operator/pkg/generated/clientset/versioned" log "github.com/sirupsen/logrus" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) -// FailoverBase ... -// gets called first on a failover -func FailoverBase(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask, restconfig *rest.Config) { +// RemovePrimaryOnRoleChangeTag sets the 'primary_on_role_change' tag to null in the +// Patroni DCS, effectively removing the tag. This is accomplished by exec'ing into +// the primary PG pod, and sending a patch request to update the appropriate data (i.e. +// the 'primary_on_role_change' tag) in the DCS. +func RemovePrimaryOnRoleChangeTag(clientset kubernetes.Interface, restconfig *rest.Config, + clusterName, namespace string) error { ctx := context.TODO() - var err error - // look up the pgcluster for this task - // in the case, the clustername is passed as a key in the - // parameters map - var clusterName string - for k := range task.Spec.Parameters { - clusterName = k - } - - cluster, err := clientset.CrunchydataV1().Pgclusters(namespace).Get(ctx, clusterName, metav1.GetOptions{}) - if err != nil { - return - } - - // create marker (clustername, namespace) - err = PatchpgtaskFailoverStatus(clientset, task, namespace) - if err != nil { - log.Errorf("could not set failover started marker for task %s cluster %s", task.Spec.Name, clusterName) - return - } + selector := config.LABEL_PG_CLUSTER + "=" + clusterName + + "," + config.LABEL_PGHA_ROLE + "=" + config.LABEL_PGHA_ROLE_PRIMARY - // get initial count of replicas --selector=pg-cluster=clusterName - selector := config.LABEL_PG_CLUSTER + "=" + clusterName - replicaList, err := clientset.CrunchydataV1().Pgreplicas(namespace).List(ctx, metav1.ListOptions{LabelSelector: selector}) - if err != nil { - log.Error(err) - return + // only consider pods that are running + options := metav1.ListOptions{ + FieldSelector: fields.OneTermEqualSelector("status.phase", string(v1.PodRunning)).String(), + LabelSelector: selector, } - log.Debugf("replica count before failover is %d", len(replicaList.Items)) - // publish event for failover - topics := make([]string, 1) - topics[0] = events.EventTopicCluster - - f := events.EventFailoverClusterFormat{ - EventHeader: events.EventHeader{ - Namespace: namespace, - Username: task.ObjectMeta.Labels[config.LABEL_PGOUSER], - Topic: topics, - Timestamp: time.Now(), - EventType: events.EventFailoverCluster, - }, - Clustername: clusterName, - Target: task.ObjectMeta.Labels[config.LABEL_TARGET], - } + pods, err := clientset.CoreV1().Pods(namespace).List(ctx, options) - err = events.Publish(f) if err != nil { log.Error(err) + return err + } else if len(pods.Items) == 0 { + return fmt.Errorf("no pods found for cluster %q", clusterName) + } else if len(pods.Items) > 1 { + log.Error("More than one primary found after completing the post-failover backup") } - - _ = Failover(cluster.ObjectMeta.Labels[config.LABEL_PG_CLUSTER_IDENTIFIER], clientset, clusterName, task, namespace, restconfig) - - // publish event for failover completed - topics = make([]string, 1) - topics[0] = events.EventTopicCluster - - g := events.EventFailoverClusterCompletedFormat{ - EventHeader: events.EventHeader{ - Namespace: namespace, - Username: task.ObjectMeta.Labels[config.LABEL_PGOUSER], - Topic: topics, - Timestamp: time.Now(), - EventType: events.EventFailoverClusterCompleted, - }, - Clustername: clusterName, - Target: task.ObjectMeta.Labels[config.LABEL_TARGET], - } - - err = events.Publish(g) + pod := pods.Items[0] + + // generate the curl command that will be run on the pod selected for the failover in order + // to trigger the failover and promote that specific pod to primary + command := make([]string, 3) + command[0] = "/bin/bash" + command[1] = "-c" + command[2] = fmt.Sprintf("curl -s 127.0.0.1:%s/config -XPATCH -d "+ + "'{\"tags\":{\"primary_on_role_change\":null}}'", config.DEFAULT_PATRONI_PORT) + + log.Debugf("running Exec command '%s' with namespace=[%s] podname=[%s] container name=[%s]", + command, namespace, pod.Name, pod.Spec.Containers[0].Name) + stdout, stderr, err := kubeapi.ExecToPodThroughAPI(restconfig, clientset, command, + pod.Spec.Containers[0].Name, pod.Name, namespace, nil) + log.Debugf("stdout=[%s] stderr=[%s]", stdout, stderr) if err != nil { log.Error(err) - } - - // remove marker -} - -func PatchpgtaskFailoverStatus(clientset pgo.Interface, oldCrd *crv1.Pgtask, namespace string) error { - ctx := context.TODO() - - // change it - oldCrd.Spec.Parameters[config.LABEL_FAILOVER_STARTED] = time.Now().Format(time.RFC3339) - - // create the patch - patchBytes, err := json.Marshal(map[string]interface{}{ - "spec": map[string]interface{}{ - "parameters": oldCrd.Spec.Parameters, - }, - }) - if err != nil { return err } - - // apply patch - _, err6 := clientset.CrunchydataV1().Pgtasks(namespace). - Patch(ctx, oldCrd.Name, types.MergePatchType, patchBytes, metav1.PatchOptions{}) - - return err6 + return nil } diff --git a/internal/operator/cluster/failoverlogic.go b/internal/operator/cluster/failoverlogic.go deleted file mode 100644 index 7391de79e4..0000000000 --- a/internal/operator/cluster/failoverlogic.go +++ /dev/null @@ -1,234 +0,0 @@ -// Package cluster holds the cluster CRD logic and definitions -// A cluster is comprised of a primary service, replica service, -// primary deployment, and replica deployment -package cluster - -/* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. - 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. -*/ - -import ( - "context" - "fmt" - "time" - - "github.com/crunchydata/postgres-operator/internal/config" - "github.com/crunchydata/postgres-operator/internal/kubeapi" - "github.com/crunchydata/postgres-operator/internal/util" - crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" - "github.com/crunchydata/postgres-operator/pkg/events" - pgo "github.com/crunchydata/postgres-operator/pkg/generated/clientset/versioned" - log "github.com/sirupsen/logrus" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" -) - -func Failover(identifier string, clientset kubeapi.Interface, clusterName string, task *crv1.Pgtask, namespace string, restconfig *rest.Config) error { - ctx := context.TODO() - - var pod *v1.Pod - var err error - target := task.ObjectMeta.Labels[config.LABEL_TARGET] - - log.Infof("Failover called on [%s] target [%s]", clusterName, target) - - pod, err = util.GetPod(clientset, target, namespace) - if err != nil { - log.Error(err) - return err - } - log.Debugf("pod selected to failover to is %s", pod.Name) - - updateFailoverStatus(clientset, task, namespace, "deleted primary deployment "+clusterName) - - // trigger the failover to the selected replica - if err := promote(pod, clientset, namespace, restconfig); err != nil { - log.Warn(err) - } - - publishPromoteEvent(namespace, task.ObjectMeta.Labels[config.LABEL_PGOUSER], clusterName, target) - - updateFailoverStatus(clientset, task, namespace, "promoting pod "+pod.Name+" target "+target) - - // relabel the deployment with primary labels - // by setting service-name=clustername - upod, err := clientset.CoreV1().Pods(namespace).Get(ctx, pod.Name, metav1.GetOptions{}) - if err != nil { - log.Error(err) - log.Error("error in getting pod during failover relabel") - return err - } - - // set the service-name label to the cluster name to match - // the primary service selector - log.Debugf("setting label on pod %s=%s", config.LABEL_SERVICE_NAME, clusterName) - - patch, err := kubeapi.NewMergePatch().Add("metadata", "labels", config.LABEL_SERVICE_NAME)(clusterName).Bytes() - if err == nil { - log.Debugf("patching pod %s: %s", upod.Name, patch) - _, err = clientset.CoreV1().Pods(namespace). - Patch(ctx, upod.Name, types.MergePatchType, patch, metav1.PatchOptions{}) - } - if err != nil { - log.Error(err) - log.Error("error in updating pod during failover relabel") - return err - } - - targetDepName := upod.ObjectMeta.Labels[config.LABEL_DEPLOYMENT_NAME] - log.Debugf("patching deployment %s: %s", targetDepName, patch) - _, err = clientset.AppsV1().Deployments(namespace). - Patch(ctx, targetDepName, types.MergePatchType, patch, metav1.PatchOptions{}) - if err != nil { - log.Error(err) - log.Error("error in updating deployment during failover relabel") - return err - } - - updateFailoverStatus(clientset, task, namespace, "updating label deployment...pod "+pod.Name+"was the failover target...failover completed") - - // update the pgcluster current-primary to new deployment name - cluster, err := clientset.CrunchydataV1().Pgclusters(namespace).Get(ctx, clusterName, metav1.GetOptions{}) - if err != nil { - log.Errorf("could not find pgcluster %s with labels", clusterName) - return err - } - - // update the CRD with the new current primary. If there is an error, log it - // here, otherwise return - if err := util.CurrentPrimaryUpdate(clientset, cluster, target, namespace); err != nil { - log.Error(err) - return err - } - - return nil -} - -func updateFailoverStatus(clientset pgo.Interface, task *crv1.Pgtask, namespace, message string) { - ctx := context.TODO() - - log.Debugf("updateFailoverStatus namespace=[%s] taskName=[%s] message=[%s]", namespace, task.Name, message) - - // update the task - t, err := clientset.CrunchydataV1().Pgtasks(task.Namespace).Get(ctx, task.Name, metav1.GetOptions{}) - if err != nil { - return - } - *task = *t - - task.Status.Message = message - - t, err = clientset.CrunchydataV1().Pgtasks(task.Namespace).Update(ctx, task, metav1.UpdateOptions{}) - if err != nil { - return - } - *task = *t -} - -func promote( - pod *v1.Pod, - clientset kubernetes.Interface, - namespace string, restconfig *rest.Config) error { - // generate the curl command that will be run on the pod selected for the failover in order - // to trigger the failover and promote that specific pod to primary - command := make([]string, 3) - command[0] = "/bin/bash" - command[1] = "-c" - command[2] = fmt.Sprintf("curl -s http://127.0.0.1:%s/failover -XPOST "+ - "-d '{\"candidate\":\"%s\"}'", config.DEFAULT_PATRONI_PORT, pod.Name) - - log.Debugf("running Exec with namespace=[%s] podname=[%s] container name=[%s]", namespace, pod.Name, pod.Spec.Containers[0].Name) - stdout, stderr, err := kubeapi.ExecToPodThroughAPI(restconfig, clientset, command, pod.Spec.Containers[0].Name, pod.Name, namespace, nil) - log.Debugf("stdout=[%s] stderr=[%s]", stdout, stderr) - if err != nil { - log.Error(err) - } - - return err -} - -func publishPromoteEvent(namespace, username, clusterName, target string) { - topics := make([]string, 1) - topics[0] = events.EventTopicCluster - - f := events.EventFailoverClusterFormat{ - EventHeader: events.EventHeader{ - Namespace: namespace, - Username: username, - Topic: topics, - Timestamp: time.Now(), - EventType: events.EventFailoverCluster, - }, - Clustername: clusterName, - Target: target, - } - - err := events.Publish(f) - if err != nil { - log.Error(err.Error()) - } -} - -// RemovePrimaryOnRoleChangeTag sets the 'primary_on_role_change' tag to null in the -// Patroni DCS, effectively removing the tag. This is accomplished by exec'ing into -// the primary PG pod, and sending a patch request to update the appropriate data (i.e. -// the 'primary_on_role_change' tag) in the DCS. -func RemovePrimaryOnRoleChangeTag(clientset kubernetes.Interface, restconfig *rest.Config, - clusterName, namespace string) error { - ctx := context.TODO() - - selector := config.LABEL_PG_CLUSTER + "=" + clusterName + - "," + config.LABEL_PGHA_ROLE + "=" + config.LABEL_PGHA_ROLE_PRIMARY - - // only consider pods that are running - options := metav1.ListOptions{ - FieldSelector: fields.OneTermEqualSelector("status.phase", string(v1.PodRunning)).String(), - LabelSelector: selector, - } - - pods, err := clientset.CoreV1().Pods(namespace).List(ctx, options) - - if err != nil { - log.Error(err) - return err - } else if len(pods.Items) == 0 { - return fmt.Errorf("no pods found for cluster %q", clusterName) - } else if len(pods.Items) > 1 { - log.Error("More than one primary found after completing the post-failover backup") - } - pod := pods.Items[0] - - // generate the curl command that will be run on the pod selected for the failover in order - // to trigger the failover and promote that specific pod to primary - command := make([]string, 3) - command[0] = "/bin/bash" - command[1] = "-c" - command[2] = fmt.Sprintf("curl -s 127.0.0.1:%s/config -XPATCH -d "+ - "'{\"tags\":{\"primary_on_role_change\":null}}'", config.DEFAULT_PATRONI_PORT) - - log.Debugf("running Exec command '%s' with namespace=[%s] podname=[%s] container name=[%s]", - command, namespace, pod.Name, pod.Spec.Containers[0].Name) - stdout, stderr, err := kubeapi.ExecToPodThroughAPI(restconfig, clientset, command, - pod.Spec.Containers[0].Name, pod.Name, namespace, nil) - log.Debugf("stdout=[%s] stderr=[%s]", stdout, stderr) - if err != nil { - log.Error(err) - return err - } - return nil -} diff --git a/internal/operator/cluster/rolling.go b/internal/operator/cluster/rolling.go index 39d50dff10..0811fb3d99 100644 --- a/internal/operator/cluster/rolling.go +++ b/internal/operator/cluster/rolling.go @@ -24,7 +24,6 @@ import ( "github.com/crunchydata/postgres-operator/internal/config" "github.com/crunchydata/postgres-operator/internal/kubeapi" "github.com/crunchydata/postgres-operator/internal/operator" - "github.com/crunchydata/postgres-operator/internal/util" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" log "github.com/sirupsen/logrus" @@ -125,7 +124,7 @@ func RollingUpdate(clientset kubeapi.Interface, restConfig *rest.Config, cluster // replicas that have the updated Deployment state. if len(instances[deploymentTypeReplica]) > 0 && len(instances[deploymentTypePrimary]) == 1 { // if the switchover fails, warn that it failed but continue on - if err := switchover(clientset, restConfig, cluster); err != nil { + if err := operator.Switchover(clientset, restConfig, cluster, ""); err != nil { log.Warnf("switchover failed: %s", err.Error()) } } @@ -233,52 +232,6 @@ func generatePostgresReadyCommand(port string) []string { return []string{"pg_isready", "-p", port} } -// generatePostgresSwitchoverCommand creates the command that is used to issue -// a switchover (demote a primary, promote a replica). Takes the name of the -// cluster; Patroni will choose the best candidate to switchover to -func generatePostgresSwitchoverCommand(clusterName string) []string { - return []string{"patronictl", "switchover", "--force", clusterName} -} - -// switchover performs a controlled switchover within a PostgreSQL cluster, i.e. -// demoting a primary and promoting a replica. The method works as such: -// -// 1. The function looks for all available replicas as well as the current -// primary. We look up the primary for convenience to avoid various API calls -// -// 2. We then search over the list to find both a primary and a suitable -// candidate for promotion. A candidate is suitable if: -// - It is on the latest timeline -// - It has the least amount of replication lag -// -// This is done to limit the risk of data loss. -// -// If either a primary or candidate is **not** found, we do not switch over. -// -// 3. If all of the above works successfully, a switchover is attempted. -func switchover(clientset kubernetes.Interface, restConfig *rest.Config, cluster *crv1.Pgcluster) error { - // we want to find a Pod to execute the switchover command on, i.e. the - // primary - pod, err := util.GetPrimaryPod(clientset, cluster) - if err != nil { - return err - } - - // good to generally log which instances are being used in the switchover - log.Infof("controlled switchover started for cluster %q", cluster.Name) - - cmd := generatePostgresSwitchoverCommand(cluster.Name) - if _, stderr, err := kubeapi.ExecToPodThroughAPI(restConfig, clientset, - cmd, "database", pod.Name, cluster.Namespace, nil); err != nil { - return fmt.Errorf(stderr) - } - - log.Infof("controlled switchover completed for cluster %q", cluster.Name) - - // and that's all - return nil -} - // waitForPostgresInstance waits for a PostgreSQL instance within a Pod is ready // to accept connections func waitForPostgresInstance(clientset kubernetes.Interface, restConfig *rest.Config, diff --git a/internal/operator/switchover.go b/internal/operator/switchover.go new file mode 100644 index 0000000000..bdffd268d2 --- /dev/null +++ b/internal/operator/switchover.go @@ -0,0 +1,156 @@ +package operator + +/* + Copyright 2021 Crunchy Data Solutions, Inc. + 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. +*/ + +import ( + "context" + "fmt" + + "github.com/crunchydata/postgres-operator/internal/config" + "github.com/crunchydata/postgres-operator/internal/kubeapi" + "github.com/crunchydata/postgres-operator/internal/util" + crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" + log "github.com/sirupsen/logrus" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" +) + +// switchover performs a controlled switchover within a PostgreSQL cluster, i.e. +// demoting a primary and promoting a replica. There are two types of switchover +// methods that can be invoked. +// +// Method #1: Automatic Choice +// +// The switchover command invokves Patroni which works as such: +// +// 1. The function looks for all available replicas as well as the current +// primary. We look up the primary for convenience to avoid various API calls +// +// 2. We then search over the list to find both a primary and a suitable +// candidate for promotion. A candidate is suitable if: +// +// - It is on the latest timeline +// - It has the least amount of replication lag +// +// This is done to limit the risk of data loss. +// +// If either a primary or candidate is **not** found, we do not switch over. +// +// 3. If all of the above works successfully, a switchover is attempted. +// +// Method #2: Targeted Choice +// +// 1. If the "target" parameter, which should contain the name of the target +// instances (Deployment), is not empty then we will attempt to locate that +// target Pod. +// +// 2. The target Pod name, called the candidate is passed into the switchover +// command generation function, and then is ultimately used in the switchover. +func Switchover(clientset kubernetes.Interface, restConfig *rest.Config, cluster *crv1.Pgcluster, target string) error { + var ( + candidate string + err error + pod *v1.Pod + ) + + // the method to get the pod is dictated by whether or not there is a target + // specified. + // + // If target is specified, then we will attempt to get the Pod that + // represents that target. + // + // If it is not specified, then we will attempt to get the primary pod + // + // If either errors, we will return an error + if target != "" { + pod, err = getCandidatePod(clientset, cluster, target) + candidate = pod.Name + } else { + pod, err = util.GetPrimaryPod(clientset, cluster) + } + + if err != nil { + return err + } + + // generate the command + cmd := generatePostgresSwitchoverCommand(cluster.Name, candidate) + + // good to generally log which instances are being used in the switchover + log.Infof("controlled switchover started for cluster %q", cluster.Name) + + if _, stderr, err := kubeapi.ExecToPodThroughAPI(restConfig, clientset, + cmd, "database", pod.Name, cluster.Namespace, nil); err != nil { + return fmt.Errorf(stderr) + } + + log.Infof("controlled switchover completed for cluster %q", cluster.Name) + + // and that's all + return nil +} + +// generatePostgresSwitchoverCommand creates the command that is used to issue +// a switchover (demote a primary, promote a replica). +// +// There are two ways to run this command: +// +// 1. Pass in only a clusterName. Patroni will select the best candidate +// 2. Pass in a clusterName AND a target candidate name, which has to be the +// name of a Pod +func generatePostgresSwitchoverCommand(clusterName, candidate string) []string { + cmd := []string{"patronictl", "switchover", "--force", clusterName} + + if candidate != "" { + cmd = append(cmd, "--candidate", candidate) + } + + return cmd +} + +// getCandidatePod tries to get the candidate Pod for a switchover. If such a +// Pod cannot be found, we likely cannot use the instance as a switchover +// candidate. +func getCandidatePod(clientset kubernetes.Interface, cluster *crv1.Pgcluster, candidateName string) (*v1.Pod, error) { + ctx := context.TODO() + // ensure the Pod is part of the cluster and is running + options := metav1.ListOptions{ + FieldSelector: fields.OneTermEqualSelector("status.phase", string(v1.PodRunning)).String(), + LabelSelector: fields.AndSelectors( + fields.OneTermEqualSelector(config.LABEL_PG_CLUSTER, cluster.Name), + fields.OneTermEqualSelector(config.LABEL_PG_DATABASE, config.LABEL_TRUE), + fields.OneTermEqualSelector(config.LABEL_DEPLOYMENT_NAME, candidateName), + ).String(), + } + + pods, err := clientset.CoreV1().Pods(cluster.Namespace).List(ctx, options) + if err != nil { + return nil, err + } + + // if no Pods are found, then also return an error as we then cannot switch + // over to this instance + if len(pods.Items) == 0 { + return nil, fmt.Errorf("no pods found for instance %s", candidateName) + } + + // there is an outside chance the list returns multiple Pods, so just return + // the first one + return &pods.Items[0], nil +} diff --git a/internal/operator/switchover_test.go b/internal/operator/switchover_test.go new file mode 100644 index 0000000000..9d9abba2ba --- /dev/null +++ b/internal/operator/switchover_test.go @@ -0,0 +1,45 @@ +package operator + +/* + Copyright 2021 Crunchy Data Solutions, Inc. + 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. +*/ + +import ( + "reflect" + "testing" +) + +func TestGeneratePostgresSwitchoverCommand(t *testing.T) { + clusterName := "hippo" + candidate := "" + + t.Run("no candidate", func(t *testing.T) { + expected := []string{"patronictl", "switchover", "--force", clusterName} + actual := generatePostgresSwitchoverCommand(clusterName, candidate) + + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected: %v actual: %v", expected, actual) + } + }) + + t.Run("candidate", func(t *testing.T) { + candidate = "hippo-abc-123" + expected := []string{"patronictl", "switchover", "--force", clusterName, "--candidate", candidate} + actual := generatePostgresSwitchoverCommand(clusterName, candidate) + + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected: %v actual: %v", expected, actual) + } + }) +} diff --git a/internal/util/failover.go b/internal/util/failover.go index 37a899e7f8..7c530d93b1 100644 --- a/internal/util/failover.go +++ b/internal/util/failover.go @@ -18,7 +18,6 @@ package util import ( "context" "encoding/json" - "errors" "fmt" "github.com/crunchydata/postgres-operator/internal/config" @@ -96,43 +95,6 @@ const ( // replication lag var instanceInfoCommand = []string{"patronictl", "list", "-f", "json"} -// GetPod determines the best target to fail to -func GetPod(clientset kubernetes.Interface, deploymentName, namespace string) (*v1.Pod, error) { - ctx := context.TODO() - - var err error - var pod *v1.Pod - var pods *v1.PodList - - selector := config.LABEL_DEPLOYMENT_NAME + "=" + deploymentName + "," + config.LABEL_PGHA_ROLE + "=replica" - pods, err = clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{LabelSelector: selector}) - if err != nil { - return pod, err - } - if len(pods.Items) != 1 { - return pod, errors.New("could not determine which pod to failover to") - } - - for i := range pods.Items { - pod = &pods.Items[i] - } - - found := false - - // make sure the pod has a database container it it - for _, c := range pod.Spec.Containers { - if c.Name == "database" { - found = true - } - } - - if !found { - return pod, errors.New("could not find a database container in the pod") - } - - return pod, err -} - // ReplicationStatus is responsible for retrieving and returning the replication // information about the status of the replicas in a PostgreSQL cluster. It // executes into a single replica pod and leverages the functionality of Patroni diff --git a/pkg/apis/crunchydata.com/v1/task.go b/pkg/apis/crunchydata.com/v1/task.go index c7eb9e4605..58340cc900 100644 --- a/pkg/apis/crunchydata.com/v1/task.go +++ b/pkg/apis/crunchydata.com/v1/task.go @@ -24,8 +24,6 @@ const PgtaskResourcePlural = "pgtasks" const ( PgtaskDeleteData = "delete-data" - PgtaskFailover = "failover" - PgtaskAutoFailover = "autofailover" PgtaskAddPolicies = "addpolicies" PgtaskRollingUpdate = "rolling update" ) diff --git a/pkg/apiservermsgs/failovermsgs.go b/pkg/apiservermsgs/failovermsgs.go index b51b11c3e4..e7c37d30ab 100644 --- a/pkg/apiservermsgs/failovermsgs.go +++ b/pkg/apiservermsgs/failovermsgs.go @@ -37,8 +37,7 @@ type QueryFailoverResponse struct { // CreateFailoverResponse ... // swagger:model type CreateFailoverResponse struct { - Results []string - Targets string + Results string Status } diff --git a/pkg/events/eventtype.go b/pkg/events/eventtype.go index 8a2031d08b..ebecda8055 100644 --- a/pkg/events/eventtype.go +++ b/pkg/events/eventtype.go @@ -45,8 +45,6 @@ const ( EventScaleClusterFailure = "ScaleClusterFailure" EventScaleDownCluster = "ScaleDownCluster" EventShutdownCluster = "ShutdownCluster" - EventFailoverCluster = "FailoverCluster" - EventFailoverClusterCompleted = "FailoverClusterCompleted" EventRestoreCluster = "RestoreCluster" EventRestoreClusterCompleted = "RestoreClusterCompleted" EventUpgradeCluster = "UpgradeCluster" @@ -202,38 +200,6 @@ func (lvl EventScaleDownClusterFormat) String() string { return msg } -//---------------------------- -type EventFailoverClusterFormat struct { - EventHeader `json:"eventheader"` - Clustername string `json:"clustername"` - Target string `json:"target"` -} - -func (p EventFailoverClusterFormat) GetHeader() EventHeader { - return p.EventHeader -} - -func (lvl EventFailoverClusterFormat) String() string { - msg := fmt.Sprintf("Event %s (failover) - clustername %s - target %s", lvl.EventHeader, lvl.Clustername, lvl.Target) - return msg -} - -//---------------------------- -type EventFailoverClusterCompletedFormat struct { - EventHeader `json:"eventheader"` - Clustername string `json:"clustername"` - Target string `json:"target"` -} - -func (p EventFailoverClusterCompletedFormat) GetHeader() EventHeader { - return p.EventHeader -} - -func (lvl EventFailoverClusterCompletedFormat) String() string { - msg := fmt.Sprintf("Event %s (failover completed) - clustername %s - target %s", lvl.EventHeader, lvl.Clustername, lvl.Target) - return msg -} - //---------------------------- type EventUpgradeClusterFormat struct { EventHeader `json:"eventheader"` From a4dc532f363a34559b83fc9dea3ca7903da16629 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 4 Jan 2021 09:46:40 -0500 Subject: [PATCH 114/276] Modify role change check command call This moves away from making a direct call to the API and instead leverages the command to update the tag on a role change. --- internal/operator/cluster/failover.go | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/internal/operator/cluster/failover.go b/internal/operator/cluster/failover.go index 67d43e3c05..748c9f1b01 100644 --- a/internal/operator/cluster/failover.go +++ b/internal/operator/cluster/failover.go @@ -32,6 +32,9 @@ import ( "k8s.io/client-go/rest" ) +var roleChangeCmd = []string{"patronictl", "edit-config", "--force", + "--set", "tags.primary_on_role_change=null"} + // RemovePrimaryOnRoleChangeTag sets the 'primary_on_role_change' tag to null in the // Patroni DCS, effectively removing the tag. This is accomplished by exec'ing into // the primary PG pod, and sending a patch request to update the appropriate data (i.e. @@ -61,17 +64,11 @@ func RemovePrimaryOnRoleChangeTag(clientset kubernetes.Interface, restconfig *re } pod := pods.Items[0] - // generate the curl command that will be run on the pod selected for the failover in order - // to trigger the failover and promote that specific pod to primary - command := make([]string, 3) - command[0] = "/bin/bash" - command[1] = "-c" - command[2] = fmt.Sprintf("curl -s 127.0.0.1:%s/config -XPATCH -d "+ - "'{\"tags\":{\"primary_on_role_change\":null}}'", config.DEFAULT_PATRONI_PORT) - + // execute the command that will be run on the pod selected for the failover + // in order to trigger the failover and promote that specific pod to primary log.Debugf("running Exec command '%s' with namespace=[%s] podname=[%s] container name=[%s]", - command, namespace, pod.Name, pod.Spec.Containers[0].Name) - stdout, stderr, err := kubeapi.ExecToPodThroughAPI(restconfig, clientset, command, + roleChangeCmd, namespace, pod.Name, pod.Spec.Containers[0].Name) + stdout, stderr, err := kubeapi.ExecToPodThroughAPI(restconfig, clientset, roleChangeCmd, pod.Spec.Containers[0].Name, pod.Name, namespace, nil) log.Debugf("stdout=[%s] stderr=[%s]", stdout, stderr) if err != nil { From 464286155c9d5dbdc5e068a165817801654b1912 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 4 Jan 2021 16:34:22 -0500 Subject: [PATCH 115/276] Enable user-defined forced failovers While this was the default behavior in Operator's past when using `pgo failover`, a previous commit changed the Operator internals to leverage a "controlled switchover" which is a bit nicer (i.e. it only works if there is a healthy instance to fail over to). However, there are situations where one must force a failover, and as such, we need to allow for one to do so. This adds the `--force` flag to `pgo failover` to allow for a forced failover. Note that `--target` must explicitly be set when forcing a failover. --- cmd/pgo/cmd/failover.go | 26 +++++-- cmd/pgo/cmd/flags.go | 11 ++- .../pgo-client/reference/pgo_failover.md | 3 +- .../apiserver/failoverservice/failoverimpl.go | 70 +++++++++++++----- internal/controller/job/backresthandler.go | 7 +- internal/operator/common.go | 44 +++++++++++ internal/operator/{cluster => }/failover.go | 74 ++++++++++++++++++- internal/operator/failover_test.go | 45 +++++++++++ internal/operator/switchover.go | 59 ++------------- pkg/apiservermsgs/failovermsgs.go | 11 ++- 10 files changed, 259 insertions(+), 91 deletions(-) rename internal/operator/{cluster => }/failover.go (53%) create mode 100644 internal/operator/failover_test.go diff --git a/cmd/pgo/cmd/failover.go b/cmd/pgo/cmd/failover.go index b67cd1d2d1..b6eb41e735 100644 --- a/cmd/pgo/cmd/failover.go +++ b/cmd/pgo/cmd/failover.go @@ -19,6 +19,7 @@ package cmd import ( "fmt" "os" + "strings" "github.com/crunchydata/postgres-operator/cmd/pgo/api" "github.com/crunchydata/postgres-operator/cmd/pgo/util" @@ -60,8 +61,10 @@ var failoverCmd = &cobra.Command{ func init() { RootCmd.AddCommand(failoverCmd) - failoverCmd.Flags().BoolVarP(&Query, "query", "", false, "Prints the list of failover candidates.") + failoverCmd.Flags().BoolVar(&Force, "force", false, "Force the failover to occur, regardless "+ + "of the health of the target instance. Must be used with \"--target\".") failoverCmd.Flags().BoolVar(&NoPrompt, "no-prompt", false, "No command line confirmation.") + failoverCmd.Flags().BoolVar(&Query, "query", false, "Prints the list of failover candidates.") failoverCmd.Flags().StringVarP(&Target, "target", "", "", "The replica target which the failover will occur on.") } @@ -69,20 +72,27 @@ func init() { func createFailover(args []string, ns string) { log.Debugf("createFailover called %v", args) - request := new(msgs.CreateFailoverRequest) - request.Namespace = ns - request.ClusterName = args[0] - request.Target = Target - request.ClientVersion = msgs.PGO_VERSION + request := &msgs.CreateFailoverRequest{ + ClientVersion: msgs.PGO_VERSION, + ClusterName: args[0], + Force: Force, + Namespace: ns, + Target: Target, + } response, err := api.CreateFailover(httpclient, &SessionCredentials, request) if err != nil { fmt.Println("Error: " + err.Error()) - os.Exit(2) + os.Exit(1) } if response.Status.Code != msgs.Ok { - fmt.Println("Error: " + response.Status.Msg) + fmt.Println("Error:", strings.ReplaceAll(response.Status.Msg, "Error: ", "")) + + if strings.Contains(response.Status.Msg, "no primary") { + fmt.Println(`Hint: Try using the "--force" flag`) + } + os.Exit(1) } diff --git a/cmd/pgo/cmd/flags.go b/cmd/pgo/cmd/flags.go index bdaf760942..a91b3daa14 100644 --- a/cmd/pgo/cmd/flags.go +++ b/cmd/pgo/cmd/flags.go @@ -22,7 +22,16 @@ var DeleteData bool // even after a cluster is deleted. This is DEPRECATED var KeepData bool -var Query bool +var ( + // Force indicates that the "force" action should be taken for that step. This + // is different than NoPrompt as "Force" is for indicating that the API server + // must try at all costs + Force bool + + // Query indicates that the attempted request is "querying" information + // instead of taking some action + Query bool +) var ( Target string diff --git a/docs/content/pgo-client/reference/pgo_failover.md b/docs/content/pgo-client/reference/pgo_failover.md index ea65e16f04..c81b3bfd92 100644 --- a/docs/content/pgo-client/reference/pgo_failover.md +++ b/docs/content/pgo-client/reference/pgo_failover.md @@ -23,6 +23,7 @@ pgo failover [flags] ### Options ``` + --force Force the failover to occur, regardless of the health of the target instance. Must be used with "--target". -h, --help help for failover --no-prompt No command line confirmation. --query Prints the list of failover candidates. @@ -46,4 +47,4 @@ pgo failover [flags] * [pgo](/pgo-client/reference/pgo/) - The pgo command line interface. -###### Auto generated by spf13/cobra on 1-Jan-2021 +###### Auto generated by spf13/cobra on 4-Jan-2021 diff --git a/internal/apiserver/failoverservice/failoverimpl.go b/internal/apiserver/failoverservice/failoverimpl.go index 983496b7ed..7d75420d44 100644 --- a/internal/apiserver/failoverservice/failoverimpl.go +++ b/internal/apiserver/failoverservice/failoverimpl.go @@ -19,6 +19,7 @@ import ( "context" "errors" "fmt" + "strings" "github.com/crunchydata/postgres-operator/internal/apiserver" "github.com/crunchydata/postgres-operator/internal/config" @@ -26,8 +27,11 @@ import ( "github.com/crunchydata/postgres-operator/internal/util" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" + log "github.com/sirupsen/logrus" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" ) // CreateFailover is the API endpoint for triggering a manual failover of a @@ -60,18 +64,24 @@ func CreateFailover(request *msgs.CreateFailoverRequest, ns, pgouser string) msg return resp } - if request.Target != "" { - if err := isValidFailoverTarget(request.Target, request.ClusterName, ns); err != nil { - resp.Status.Code = msgs.Error - resp.Status.Msg = err.Error() - return resp - } + if err := isValidFailoverTarget(request); err != nil { + resp.Status.Code = msgs.Error + resp.Status.Msg = err.Error() + return resp } - // perform the switchover - if err := operator.Switchover(apiserver.Clientset, apiserver.RESTConfig, cluster, request.Target); err != nil { + // perform the switchover or failover, depending on which flag is selected + // if we are forcing the failover, we need to use "Failover", otherwise we + // perform a controlled switchover + if request.Force { + err = operator.Failover(apiserver.Clientset, apiserver.RESTConfig, cluster, request.Target) + } else { + err = operator.Switchover(apiserver.Clientset, apiserver.RESTConfig, cluster, request.Target) + } + + if err != nil { resp.Status.Code = msgs.Error - resp.Status.Msg = err.Error() + resp.Status.Msg = strings.ReplaceAll(err.Error(), "master", "primary") return resp } @@ -161,31 +171,53 @@ func validateClusterName(clusterName, ns string) (*crv1.Pgcluster, error) { // specified, and then ensuring the PG pod created by the deployment is not the current primary. // If the deployment is not found, or if the pod is the current primary, an error will be returned. // Otherwise the deployment is returned. -func isValidFailoverTarget(deployName, clusterName, ns string) error { +func isValidFailoverTarget(request *msgs.CreateFailoverRequest) error { ctx := context.TODO() + // if we're not forcing a failover and the target is blank, we can + // return here + // However, if we are forcing a failover and the target is blank, then we do + // have an error + if request.Target == "" { + if !request.Force { + return nil + } + + return fmt.Errorf("target is required when forcing a failover.") + } + // Using the following label selector, ensure the deployment specified using deployName exists in the // cluster specified using clusterName: // pg-cluster=clusterName,deployment-name=deployName - selector := config.LABEL_PG_CLUSTER + "=" + clusterName + "," + config.LABEL_DEPLOYMENT_NAME + "=" + deployName - deployments, err := apiserver.Clientset. - AppsV1().Deployments(ns). - List(ctx, metav1.ListOptions{LabelSelector: selector}) + options := metav1.ListOptions{ + LabelSelector: fields.AndSelectors( + fields.OneTermEqualSelector(config.LABEL_PG_CLUSTER, request.ClusterName), + fields.OneTermEqualSelector(config.LABEL_DEPLOYMENT_NAME, request.Target), + ).String(), + } + deployments, err := apiserver.Clientset.AppsV1().Deployments(request.Namespace).List(ctx, options) + if err != nil { log.Error(err) return err } else if len(deployments.Items) == 0 { - return fmt.Errorf("no target found named %s", deployName) + return fmt.Errorf("no target found named %s", request.Target) } else if len(deployments.Items) > 1 { - return fmt.Errorf("more than one target found named %s", deployName) + return fmt.Errorf("more than one target found named %s", request.Target) } // Using the following label selector, determine if the target specified is the current // primary for the cluster and return an error if it is: // pg-cluster=clusterName,deployment-name=deployName,role=primary - selector = config.LABEL_PG_CLUSTER + "=" + clusterName + "," + config.LABEL_DEPLOYMENT_NAME + "=" + deployName + - "," + config.LABEL_PGHA_ROLE + "=" + config.LABEL_PGHA_ROLE_PRIMARY - pods, _ := apiserver.Clientset.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{LabelSelector: selector}) + options.FieldSelector = fields.OneTermEqualSelector("status.phase", string(v1.PodRunning)).String() + options.LabelSelector = fields.AndSelectors( + fields.OneTermEqualSelector(config.LABEL_PG_CLUSTER, request.ClusterName), + fields.OneTermEqualSelector(config.LABEL_DEPLOYMENT_NAME, request.Target), + fields.OneTermEqualSelector(config.LABEL_PGHA_ROLE, config.LABEL_PGHA_ROLE_PRIMARY), + ).String() + + pods, _ := apiserver.Clientset.CoreV1().Pods(request.Namespace).List(ctx, options) + if len(pods.Items) > 0 { return fmt.Errorf("The primary database cannot be selected as a failover target") } diff --git a/internal/controller/job/backresthandler.go b/internal/controller/job/backresthandler.go index c7f585cb5e..2359fa11c0 100644 --- a/internal/controller/job/backresthandler.go +++ b/internal/controller/job/backresthandler.go @@ -26,8 +26,8 @@ import ( "github.com/crunchydata/postgres-operator/internal/config" "github.com/crunchydata/postgres-operator/internal/controller" "github.com/crunchydata/postgres-operator/internal/kubeapi" + "github.com/crunchydata/postgres-operator/internal/operator" "github.com/crunchydata/postgres-operator/internal/operator/backrest" - clusteroperator "github.com/crunchydata/postgres-operator/internal/operator/cluster" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" ) @@ -92,9 +92,8 @@ func (c *Controller) handleBackrestBackupUpdate(job *apiv1.Job) error { job.ObjectMeta.Namespace) } else if labels[config.LABEL_PGHA_BACKUP_TYPE] == crv1.BackupTypeFailover { - err := clusteroperator.RemovePrimaryOnRoleChangeTag(c.Client, c.Client.Config, - labels[config.LABEL_PG_CLUSTER], job.ObjectMeta.Namespace) - if err != nil { + if err := operator.RemovePrimaryOnRoleChangeTag(c.Client, c.Client.Config, + labels[config.LABEL_PG_CLUSTER], job.ObjectMeta.Namespace); err != nil { log.Error(err) return err } diff --git a/internal/operator/common.go b/internal/operator/common.go index 20734af392..138436b4d2 100644 --- a/internal/operator/common.go +++ b/internal/operator/common.go @@ -32,6 +32,7 @@ import ( v1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" "k8s.io/client-go/kubernetes" ) @@ -302,6 +303,49 @@ func SetContainerImageOverride(containerImageName string, container *v1.Containe } } +// getCandidatePod tries to get the candidate Pod for a switchover or failover. +// If "candidateName" is provided, it will seek out the specific PostgreSQL +// instance. Otherwise, it will just attempt to find a running Pod. +// +// If such a Pod cannot be found, we likely cannot use the instance for a +// switchover for failover candidate as it is not running. +func getCandidatePod(clientset kubernetes.Interface, cluster *crv1.Pgcluster, candidateName string) (*v1.Pod, error) { + ctx := context.TODO() + + // build the label selector. we are looking for any PostgreSQL instance within + // this cluster, so that part is easy + labelSelector := fields.Set{ + config.LABEL_PG_CLUSTER: cluster.Name, + config.LABEL_PG_DATABASE: config.LABEL_TRUE, + } + + // if a candidateName is supplied, use that as part of the label selector to + // find the candidate Pod + if candidateName != "" { + labelSelector[config.LABEL_DEPLOYMENT_NAME] = candidateName + } + + // ensure the Pod is part of the cluster and is running + options := metav1.ListOptions{ + FieldSelector: fields.OneTermEqualSelector("status.phase", string(v1.PodRunning)).String(), + LabelSelector: labelSelector.String(), + } + + pods, err := clientset.CoreV1().Pods(cluster.Namespace).List(ctx, options) + if err != nil { + return nil, err + } + + // if no Pods are found, then also return an error as we then cannot switch + // over to this instance + if len(pods.Items) == 0 { + return nil, fmt.Errorf("no pods found for instance %s", candidateName) + } + + // the list returns multiple Pods, so just return the first one + return &pods.Items[0], nil +} + // initializeContainerImageOverrides initializes the container image overrides // that could be set if there are any `RELATED_IMAGE_*` environmental variables func initializeContainerImageOverrides() { diff --git a/internal/operator/cluster/failover.go b/internal/operator/failover.go similarity index 53% rename from internal/operator/cluster/failover.go rename to internal/operator/failover.go index 748c9f1b01..1334a09989 100644 --- a/internal/operator/cluster/failover.go +++ b/internal/operator/failover.go @@ -1,7 +1,4 @@ -// Package cluster holds the cluster CRD logic and definitions -// A cluster is comprised of a primary service, replica service, -// primary deployment, and replica deployment -package cluster +package operator /* Copyright 2018 - 2021 Crunchy Data Solutions, Inc. @@ -24,6 +21,7 @@ import ( "github.com/crunchydata/postgres-operator/internal/config" "github.com/crunchydata/postgres-operator/internal/kubeapi" + crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" log "github.com/sirupsen/logrus" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -35,6 +33,56 @@ import ( var roleChangeCmd = []string{"patronictl", "edit-config", "--force", "--set", "tags.primary_on_role_change=null"} +// Failover performs a failover to a PostgreSQL cluster, which is effectively +// a "forced switchover." In other words, failover will force ensure that +// there is a primary available. +// +// NOTE: This is reserve as the "last resort" case. If you want a controlled +// failover, you want "Switchover". +// +// A target must be specified. The target should contain the name of the target +// instances (Deployment), is not empty then we will attempt to locate that +// target Pod. +// +// The target Pod name, called the candidate is passed into the failover +// command generation function, and then is ultimately used in the failover. +func Failover(clientset kubernetes.Interface, restConfig *rest.Config, cluster *crv1.Pgcluster, target string) error { + // ensure target is not empty + if target == "" { + return fmt.Errorf("failover requires a target instance to be specified.") + } + + // When the target is specified, we will attempt to get the Pod that + // represents that target. + // + // If it is not specified, then we will attempt to get any Pod. + // + // If either errors, we will return an error + pod, err := getCandidatePod(clientset, cluster, target) + + if err != nil { + return err + } + + candidate := pod.Name + + // generate the command + cmd := generatePostgresFailoverCommand(cluster.Name, candidate) + + // good to generally log which instances are being used in the failover + log.Infof("failover started for cluster %q", cluster.Name) + + if _, stderr, err := kubeapi.ExecToPodThroughAPI(restConfig, clientset, + cmd, "database", pod.Name, cluster.Namespace, nil); err != nil { + return fmt.Errorf(stderr) + } + + log.Infof("failover completed for cluster %q", cluster.Name) + + // and that's all + return nil +} + // RemovePrimaryOnRoleChangeTag sets the 'primary_on_role_change' tag to null in the // Patroni DCS, effectively removing the tag. This is accomplished by exec'ing into // the primary PG pod, and sending a patch request to update the appropriate data (i.e. @@ -77,3 +125,21 @@ func RemovePrimaryOnRoleChangeTag(clientset kubernetes.Interface, restconfig *re } return nil } + +// generatePostgresFailoverCommand creates the command that is used to issue +// a failover command (ensure that there is a promoted primary). +// +// There are two ways to run this command: +// +// 1. Pass in only a clusterName. Patroni will select the best candidate +// 2. Pass in a clusterName AND a target candidate name, which has to be the +// name of a Pod +func generatePostgresFailoverCommand(clusterName, candidate string) []string { + cmd := []string{"patronictl", "failover", "--force", clusterName} + + if candidate != "" { + cmd = append(cmd, "--candidate", candidate) + } + + return cmd +} diff --git a/internal/operator/failover_test.go b/internal/operator/failover_test.go new file mode 100644 index 0000000000..67f3be31ef --- /dev/null +++ b/internal/operator/failover_test.go @@ -0,0 +1,45 @@ +package operator + +/* + Copyright 2021 Crunchy Data Solutions, Inc. + 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. +*/ + +import ( + "reflect" + "testing" +) + +func TestGeneratePostgresFailoverCommand(t *testing.T) { + clusterName := "hippo" + candidate := "" + + t.Run("no candidate", func(t *testing.T) { + expected := []string{"patronictl", "failover", "--force", clusterName} + actual := generatePostgresFailoverCommand(clusterName, candidate) + + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected: %v actual: %v", expected, actual) + } + }) + + t.Run("candidate", func(t *testing.T) { + candidate = "hippo-abc-123" + expected := []string{"patronictl", "failover", "--force", clusterName, "--candidate", candidate} + actual := generatePostgresFailoverCommand(clusterName, candidate) + + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected: %v actual: %v", expected, actual) + } + }) +} diff --git a/internal/operator/switchover.go b/internal/operator/switchover.go index bdffd268d2..6595f61208 100644 --- a/internal/operator/switchover.go +++ b/internal/operator/switchover.go @@ -16,22 +16,16 @@ package operator */ import ( - "context" "fmt" - "github.com/crunchydata/postgres-operator/internal/config" "github.com/crunchydata/postgres-operator/internal/kubeapi" - "github.com/crunchydata/postgres-operator/internal/util" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" log "github.com/sirupsen/logrus" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) -// switchover performs a controlled switchover within a PostgreSQL cluster, i.e. +// Switchover performs a controlled switchover within a PostgreSQL cluster, i.e. // demoting a primary and promoting a replica. There are two types of switchover // methods that can be invoked. // @@ -63,32 +57,26 @@ import ( // 2. The target Pod name, called the candidate is passed into the switchover // command generation function, and then is ultimately used in the switchover. func Switchover(clientset kubernetes.Interface, restConfig *rest.Config, cluster *crv1.Pgcluster, target string) error { - var ( - candidate string - err error - pod *v1.Pod - ) - // the method to get the pod is dictated by whether or not there is a target // specified. // // If target is specified, then we will attempt to get the Pod that // represents that target. // - // If it is not specified, then we will attempt to get the primary pod + // If it is not specified, then we will attempt to get any Pod. // // If either errors, we will return an error - if target != "" { - pod, err = getCandidatePod(clientset, cluster, target) - candidate = pod.Name - } else { - pod, err = util.GetPrimaryPod(clientset, cluster) - } + candidate := "" + pod, err := getCandidatePod(clientset, cluster, target) if err != nil { return err } + if target != "" { + candidate = pod.Name + } + // generate the command cmd := generatePostgresSwitchoverCommand(cluster.Name, candidate) @@ -123,34 +111,3 @@ func generatePostgresSwitchoverCommand(clusterName, candidate string) []string { return cmd } - -// getCandidatePod tries to get the candidate Pod for a switchover. If such a -// Pod cannot be found, we likely cannot use the instance as a switchover -// candidate. -func getCandidatePod(clientset kubernetes.Interface, cluster *crv1.Pgcluster, candidateName string) (*v1.Pod, error) { - ctx := context.TODO() - // ensure the Pod is part of the cluster and is running - options := metav1.ListOptions{ - FieldSelector: fields.OneTermEqualSelector("status.phase", string(v1.PodRunning)).String(), - LabelSelector: fields.AndSelectors( - fields.OneTermEqualSelector(config.LABEL_PG_CLUSTER, cluster.Name), - fields.OneTermEqualSelector(config.LABEL_PG_DATABASE, config.LABEL_TRUE), - fields.OneTermEqualSelector(config.LABEL_DEPLOYMENT_NAME, candidateName), - ).String(), - } - - pods, err := clientset.CoreV1().Pods(cluster.Namespace).List(ctx, options) - if err != nil { - return nil, err - } - - // if no Pods are found, then also return an error as we then cannot switch - // over to this instance - if len(pods.Items) == 0 { - return nil, fmt.Errorf("no pods found for instance %s", candidateName) - } - - // there is an outside chance the list returns multiple Pods, so just return - // the first one - return &pods.Items[0], nil -} diff --git a/pkg/apiservermsgs/failovermsgs.go b/pkg/apiservermsgs/failovermsgs.go index e7c37d30ab..208c7bc2ee 100644 --- a/pkg/apiservermsgs/failovermsgs.go +++ b/pkg/apiservermsgs/failovermsgs.go @@ -44,10 +44,15 @@ type CreateFailoverResponse struct { // CreateFailoverRequest ... // swagger:model type CreateFailoverRequest struct { - Namespace string - ClusterName string - Target string ClientVersion string + ClusterName string + // Force determines whether or not to force the failover. A normal failover + // request uses a switchover, which seeks a healthy option. However, "Force" + // forces the issue so to speak, and will promote either the instance that is + // the best fit or the specified target + Force bool + Namespace string + Target string } // QueryFailoverRequest ... From 3f007d583259ad166ac9a5dadd500aac5a57a6e9 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 4 Jan 2021 17:43:57 -0500 Subject: [PATCH 116/276] Move default PostgreSQL version to 13 We are close enough to 13.2 that it is OK to move up the default to 13 for this release. --- Makefile | 2 +- bin/push-ccp-to-gcr.sh | 2 +- conf/postgres-operator/pgo.yaml | 2 +- docs/config.toml | 4 ++-- docs/content/tutorial/pgbouncer.md | 4 ++-- examples/create-by-resource/fromcrd.json | 2 +- examples/helm/create-cluster/values.yaml | 2 +- examples/kustomize/createcluster/README.md | 8 ++++---- examples/kustomize/createcluster/base/pgcluster.yaml | 2 +- installers/ansible/values.yaml | 2 +- installers/gcp-marketplace/values.yaml | 2 +- installers/helm/values.yaml | 2 +- installers/kubectl/postgres-operator-ocp311.yml | 2 +- installers/kubectl/postgres-operator.yml | 2 +- installers/olm/Makefile | 2 +- 15 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index 5f5e8f06e6..c5e9fb2036 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ PGO_IMAGE_PREFIX ?= crunchydata PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) PGO_VERSION ?= 4.5.1 PGO_PG_VERSION ?= 12 -PGO_PG_FULLVERSION ?= 12.5 +PGO_PG_FULLVERSION ?= 13.1 PGO_BACKREST_VERSION ?= 2.31 PACKAGER ?= yum diff --git a/bin/push-ccp-to-gcr.sh b/bin/push-ccp-to-gcr.sh index d476c07b0b..999d355f1f 100755 --- a/bin/push-ccp-to-gcr.sh +++ b/bin/push-ccp-to-gcr.sh @@ -16,7 +16,7 @@ GCR_IMAGE_PREFIX=gcr.io/crunchy-dev-test CCP_IMAGE_PREFIX=crunchydata -CCP_IMAGE_TAG=centos7-12.5-4.5.1 +CCP_IMAGE_TAG=centos7-13.1-4.5.1 IMAGES=( crunchy-prometheus diff --git a/conf/postgres-operator/pgo.yaml b/conf/postgres-operator/pgo.yaml index 0cb8bfadc1..569f91142c 100644 --- a/conf/postgres-operator/pgo.yaml +++ b/conf/postgres-operator/pgo.yaml @@ -2,7 +2,7 @@ Cluster: CCPImagePrefix: registry.developers.crunchydata.com/crunchydata Metrics: false Badger: false - CCPImageTag: centos7-12.5-4.5.1 + CCPImageTag: centos7-13.1-4.5.1 Port: 5432 PGBadgerPort: 10000 ExporterPort: 9187 diff --git a/docs/config.toml b/docs/config.toml index dc39d49daa..7c04096c8e 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -26,9 +26,9 @@ highlightClientSide = false # set true to use highlight.pack.js instead of the d menushortcutsnewtab = true # set true to open shortcuts links to a new tab/window enableGitInfo = true operatorVersion = "4.5.1" -postgresVersion = "12.5" +postgresVersion = "13.1" postgresVersion13 = "13.1" -postgresVersion12 = "12.5" +postgresVersion12 = "13.1" postgresVersion11 = "11.10" postgresVersion10 = "10.15" postgresVersion96 = "9.6.20" diff --git a/docs/content/tutorial/pgbouncer.md b/docs/content/tutorial/pgbouncer.md index 43c2534996..a2d3b342c1 100644 --- a/docs/content/tutorial/pgbouncer.md +++ b/docs/content/tutorial/pgbouncer.md @@ -116,7 +116,7 @@ PGPASSWORD=randompassword psql -h localhost -p 5432 -U pgbouncer pgbouncer You should see something similar to this: ``` -psql (12.5, server 1.14.0/bouncer) +psql (13.1, server 1.14.0/bouncer) Type "help" for help. pgbouncer=# @@ -172,7 +172,7 @@ PGPASSWORD=securerandomlygeneratedpassword psql -h localhost -p 5432 -U testuser You should see something similar to this: ``` -psql (12.5) +psql (13.1) SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off) Type "help" for help. diff --git a/examples/create-by-resource/fromcrd.json b/examples/create-by-resource/fromcrd.json index c267e258d5..0d34b03e32 100644 --- a/examples/create-by-resource/fromcrd.json +++ b/examples/create-by-resource/fromcrd.json @@ -45,7 +45,7 @@ "supplementalgroups": "" }, "ccpimage": "crunchy-postgres-ha", - "ccpimagetag": "centos7-12.5-4.5.1", + "ccpimagetag": "centos7-13.1-4.5.1", "clustername": "fromcrd", "database": "userdb", "exporterport": "9187", diff --git a/examples/helm/create-cluster/values.yaml b/examples/helm/create-cluster/values.yaml index b0301c6205..1f1336bf64 100644 --- a/examples/helm/create-cluster/values.yaml +++ b/examples/helm/create-cluster/values.yaml @@ -4,7 +4,7 @@ # The values is for the namespace and the postgresql cluster name ccpimage: crunchy-postgres-ha ccpimageprefix: registry.developers.crunchydata.com/crunchydata -ccpimagetag: centos7-12.5-4.5.1 +ccpimagetag: centos7-13.1-4.5.1 namespace: pgo pgclustername: hippo pgoimageprefix: registry.developers.crunchydata.com/crunchydata diff --git a/examples/kustomize/createcluster/README.md b/examples/kustomize/createcluster/README.md index 3ea4c18f9f..04b6b2b7d6 100644 --- a/examples/kustomize/createcluster/README.md +++ b/examples/kustomize/createcluster/README.md @@ -44,7 +44,7 @@ pgo show cluster hippo -n pgo ``` You will see something like this if successful: ``` -cluster : hippo (crunchy-postgres-ha:centos7-12.5-4.5.1) +cluster : hippo (crunchy-postgres-ha:centos7-13.1-4.5.1) pod : hippo-8fb6bd96-j87wq (Running) on gke-xxxx-default-pool-38e946bd-257w (1/1) (primary) pvc: hippo (1Gi) deployment : hippo @@ -79,7 +79,7 @@ pgo show cluster dev-hippo -n pgo ``` You will see something like this if successful: ``` -cluster : dev-hippo (crunchy-postgres-ha:centos7-12.5-4.5.1) +cluster : dev-hippo (crunchy-postgres-ha:centos7-13.1-4.5.1) pod : dev-hippo-588d4cb746-bwrxb (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: dev-hippo (1Gi) deployment : dev-hippo @@ -113,7 +113,7 @@ pgo show cluster staging-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : staging-hippo (crunchy-postgres-ha:centos7-12.5-4.5.1) +cluster : staging-hippo (crunchy-postgres-ha:centos7-13.1-4.5.1) pod : staging-hippo-85cf6dcb65-9h748 (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: staging-hippo (1Gi) pod : staging-hippo-lnxw-cf47d8c8b-6r4wn (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (replica) @@ -154,7 +154,7 @@ pgo show cluster prod-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : prod-hippo (crunchy-postgres-ha:centos7-12.5-4.5.1) +cluster : prod-hippo (crunchy-postgres-ha:centos7-13.1-4.5.1) pod : prod-hippo-5d6dd46497-rr67c (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (primary) pvc: prod-hippo (1Gi) pod : prod-hippo-flty-84d97c8769-2pzbh (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (replica) diff --git a/examples/kustomize/createcluster/base/pgcluster.yaml b/examples/kustomize/createcluster/base/pgcluster.yaml index cf3293a73a..d1c8fb884c 100644 --- a/examples/kustomize/createcluster/base/pgcluster.yaml +++ b/examples/kustomize/createcluster/base/pgcluster.yaml @@ -42,7 +42,7 @@ spec: annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: registry.developers.crunchydata.com/crunchydata - ccpimagetag: centos7-12.5-4.5.1 + ccpimagetag: centos7-13.1-4.5.1 clustername: hippo customconfig: "" database: hippo diff --git a/installers/ansible/values.yaml b/installers/ansible/values.yaml index ebce0ed751..58d5f6debf 100644 --- a/installers/ansible/values.yaml +++ b/installers/ansible/values.yaml @@ -17,7 +17,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos7-12.5-4.5.1" +ccp_image_tag: "centos7-13.1-4.5.1" create_rbac: "true" crunchy_debug: "false" db_name: "" diff --git a/installers/gcp-marketplace/values.yaml b/installers/gcp-marketplace/values.yaml index e2ed852df1..c86f34b68a 100644 --- a/installers/gcp-marketplace/values.yaml +++ b/installers/gcp-marketplace/values.yaml @@ -10,7 +10,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos7-12.5-4.5.1" +ccp_image_tag: "centos7-13.1-4.5.1" create_rbac: "true" db_name: "" db_password_age_days: "0" diff --git a/installers/helm/values.yaml b/installers/helm/values.yaml index b2c5d441b2..536a1bcb9d 100644 --- a/installers/helm/values.yaml +++ b/installers/helm/values.yaml @@ -37,7 +37,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos7-12.5-4.5.1" +ccp_image_tag: "centos7-13.1-4.5.1" create_rbac: "true" crunchy_debug: "false" db_name: "" diff --git a/installers/kubectl/postgres-operator-ocp311.yml b/installers/kubectl/postgres-operator-ocp311.yml index 977c9ea790..f0929e4b14 100644 --- a/installers/kubectl/postgres-operator-ocp311.yml +++ b/installers/kubectl/postgres-operator-ocp311.yml @@ -44,7 +44,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos7-12.5-4.5.1" + ccp_image_tag: "centos7-13.1-4.5.1" create_rbac: "true" crunchy_debug: "false" db_name: "" diff --git a/installers/kubectl/postgres-operator.yml b/installers/kubectl/postgres-operator.yml index 971e436d20..91e05d823e 100644 --- a/installers/kubectl/postgres-operator.yml +++ b/installers/kubectl/postgres-operator.yml @@ -138,7 +138,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos7-12.5-4.5.1" + ccp_image_tag: "centos7-13.1-4.5.1" create_rbac: "true" crunchy_debug: "false" db_name: "" diff --git a/installers/olm/Makefile b/installers/olm/Makefile index ebc81698fa..36a11f149e 100644 --- a/installers/olm/Makefile +++ b/installers/olm/Makefile @@ -2,7 +2,7 @@ .SUFFIXES: CCP_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -CCP_PG_FULLVERSION ?= 12.5 +CCP_PG_FULLVERSION ?= 13.1 CCP_POSTGIS_VERSION ?= 3.0 CONTAINER ?= docker KUBECONFIG ?= $(HOME)/.kube/config From 815dae81c0cc6e62a9368d9f40abebbd4539750f Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 4 Jan 2021 17:47:43 -0500 Subject: [PATCH 117/276] Move base image default to CentOS 8 --- Makefile | 2 +- bin/push-ccp-to-gcr.sh | 2 +- conf/postgres-operator/pgo.yaml | 4 ++-- docs/config.toml | 2 +- .../advanced/crunchy-postgres-exporter.md | 4 ++-- examples/create-by-resource/fromcrd.json | 2 +- examples/envs.sh | 2 +- examples/helm/create-cluster/values.yaml | 2 +- examples/kustomize/createcluster/README.md | 8 +++---- .../createcluster/base/pgcluster.yaml | 2 +- installers/ansible/values.yaml | 4 ++-- installers/gcp-marketplace/values.yaml | 4 ++-- installers/helm/values.yaml | 4 ++-- .../kubectl/postgres-operator-ocp311.yml | 6 ++--- installers/kubectl/postgres-operator.yml | 6 ++--- installers/metrics/helm/helm_template.yaml | 2 +- installers/metrics/helm/values.yaml | 2 +- .../postgres-operator-metrics-ocp311.yml | 2 +- .../kubectl/postgres-operator-metrics.yml | 2 +- installers/olm/Makefile | 2 +- internal/util/util_test.go | 24 +++++++++---------- 21 files changed, 44 insertions(+), 44 deletions(-) diff --git a/Makefile b/Makefile index c5e9fb2036..1469e405b1 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ # Default values if not already set ANSIBLE_VERSION ?= 2.9.* PGOROOT ?= $(CURDIR) -PGO_BASEOS ?= centos7 +PGO_BASEOS ?= centos8 PGO_IMAGE_PREFIX ?= crunchydata PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) PGO_VERSION ?= 4.5.1 diff --git a/bin/push-ccp-to-gcr.sh b/bin/push-ccp-to-gcr.sh index 999d355f1f..f1565f0dfb 100755 --- a/bin/push-ccp-to-gcr.sh +++ b/bin/push-ccp-to-gcr.sh @@ -16,7 +16,7 @@ GCR_IMAGE_PREFIX=gcr.io/crunchy-dev-test CCP_IMAGE_PREFIX=crunchydata -CCP_IMAGE_TAG=centos7-13.1-4.5.1 +CCP_IMAGE_TAG=centos8-13.1-4.5.1 IMAGES=( crunchy-prometheus diff --git a/conf/postgres-operator/pgo.yaml b/conf/postgres-operator/pgo.yaml index 569f91142c..0b90394354 100644 --- a/conf/postgres-operator/pgo.yaml +++ b/conf/postgres-operator/pgo.yaml @@ -2,7 +2,7 @@ Cluster: CCPImagePrefix: registry.developers.crunchydata.com/crunchydata Metrics: false Badger: false - CCPImageTag: centos7-13.1-4.5.1 + CCPImageTag: centos8-13.1-4.5.1 Port: 5432 PGBadgerPort: 10000 ExporterPort: 9187 @@ -81,4 +81,4 @@ Storage: Pgo: Audit: false PGOImagePrefix: registry.developers.crunchydata.com/crunchydata - PGOImageTag: centos7-4.5.1 + PGOImageTag: centos8-4.5.1 diff --git a/docs/config.toml b/docs/config.toml index 7c04096c8e..cafb971a4d 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -34,7 +34,7 @@ postgresVersion10 = "10.15" postgresVersion96 = "9.6.20" postgresVersion95 = "9.5.24" postgisVersion = "3.0" -centosBase = "centos7" +centosBase = "centos8" [outputs] home = [ "HTML", "RSS", "JSON"] diff --git a/docs/content/advanced/crunchy-postgres-exporter.md b/docs/content/advanced/crunchy-postgres-exporter.md index b9b2a3ba09..16a58e5672 100644 --- a/docs/content/advanced/crunchy-postgres-exporter.md +++ b/docs/content/advanced/crunchy-postgres-exporter.md @@ -24,8 +24,8 @@ can be specified for the API to collect. For an example of a queries.yml file, s The crunchy-postgres-exporter Docker image contains the following packages (versions vary depending on PostgreSQL version): * PostgreSQL ({{< param postgresVersion13 >}}, {{< param postgresVersion12 >}}, {{< param postgresVersion11 >}}, {{< param postgresVersion10 >}}, {{< param postgresVersion96 >}} and {{< param postgresVersion95 >}}) -* CentOS7 - publicly available -* UBI7 - customers only +* CentOS 8 - publicly available +* UBI 7, UBI 8 - customers only * [PostgreSQL Exporter](https://github.com/wrouesnel/postgres_exporter) ## Environment Variables diff --git a/examples/create-by-resource/fromcrd.json b/examples/create-by-resource/fromcrd.json index 0d34b03e32..aca1a509c1 100644 --- a/examples/create-by-resource/fromcrd.json +++ b/examples/create-by-resource/fromcrd.json @@ -45,7 +45,7 @@ "supplementalgroups": "" }, "ccpimage": "crunchy-postgres-ha", - "ccpimagetag": "centos7-13.1-4.5.1", + "ccpimagetag": "centos8-13.1-4.5.1", "clustername": "fromcrd", "database": "userdb", "exporterport": "9187", diff --git a/examples/envs.sh b/examples/envs.sh index 848a7252b5..b824de2eeb 100644 --- a/examples/envs.sh +++ b/examples/envs.sh @@ -19,7 +19,7 @@ export PGO_CONF_DIR=$PGOROOT/installers/ansible/roles/pgo-operator/files # the version of the Operator you run is set by these vars export PGO_IMAGE_PREFIX=registry.developers.crunchydata.com/crunchydata -export PGO_BASEOS=centos7 +export PGO_BASEOS=centos8 export PGO_VERSION=4.5.1 export PGO_IMAGE_TAG=$PGO_BASEOS-$PGO_VERSION diff --git a/examples/helm/create-cluster/values.yaml b/examples/helm/create-cluster/values.yaml index 1f1336bf64..ce2abbf974 100644 --- a/examples/helm/create-cluster/values.yaml +++ b/examples/helm/create-cluster/values.yaml @@ -4,7 +4,7 @@ # The values is for the namespace and the postgresql cluster name ccpimage: crunchy-postgres-ha ccpimageprefix: registry.developers.crunchydata.com/crunchydata -ccpimagetag: centos7-13.1-4.5.1 +ccpimagetag: centos8-13.1-4.5.1 namespace: pgo pgclustername: hippo pgoimageprefix: registry.developers.crunchydata.com/crunchydata diff --git a/examples/kustomize/createcluster/README.md b/examples/kustomize/createcluster/README.md index 04b6b2b7d6..6eb934577b 100644 --- a/examples/kustomize/createcluster/README.md +++ b/examples/kustomize/createcluster/README.md @@ -44,7 +44,7 @@ pgo show cluster hippo -n pgo ``` You will see something like this if successful: ``` -cluster : hippo (crunchy-postgres-ha:centos7-13.1-4.5.1) +cluster : hippo (crunchy-postgres-ha:centos8-13.1-4.5.1) pod : hippo-8fb6bd96-j87wq (Running) on gke-xxxx-default-pool-38e946bd-257w (1/1) (primary) pvc: hippo (1Gi) deployment : hippo @@ -79,7 +79,7 @@ pgo show cluster dev-hippo -n pgo ``` You will see something like this if successful: ``` -cluster : dev-hippo (crunchy-postgres-ha:centos7-13.1-4.5.1) +cluster : dev-hippo (crunchy-postgres-ha:centos8-13.1-4.5.1) pod : dev-hippo-588d4cb746-bwrxb (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: dev-hippo (1Gi) deployment : dev-hippo @@ -113,7 +113,7 @@ pgo show cluster staging-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : staging-hippo (crunchy-postgres-ha:centos7-13.1-4.5.1) +cluster : staging-hippo (crunchy-postgres-ha:centos8-13.1-4.5.1) pod : staging-hippo-85cf6dcb65-9h748 (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: staging-hippo (1Gi) pod : staging-hippo-lnxw-cf47d8c8b-6r4wn (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (replica) @@ -154,7 +154,7 @@ pgo show cluster prod-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : prod-hippo (crunchy-postgres-ha:centos7-13.1-4.5.1) +cluster : prod-hippo (crunchy-postgres-ha:centos8-13.1-4.5.1) pod : prod-hippo-5d6dd46497-rr67c (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (primary) pvc: prod-hippo (1Gi) pod : prod-hippo-flty-84d97c8769-2pzbh (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (replica) diff --git a/examples/kustomize/createcluster/base/pgcluster.yaml b/examples/kustomize/createcluster/base/pgcluster.yaml index d1c8fb884c..f070683e08 100644 --- a/examples/kustomize/createcluster/base/pgcluster.yaml +++ b/examples/kustomize/createcluster/base/pgcluster.yaml @@ -42,7 +42,7 @@ spec: annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: registry.developers.crunchydata.com/crunchydata - ccpimagetag: centos7-13.1-4.5.1 + ccpimagetag: centos8-13.1-4.5.1 clustername: hippo customconfig: "" database: hippo diff --git a/installers/ansible/values.yaml b/installers/ansible/values.yaml index 58d5f6debf..2675ac8eb6 100644 --- a/installers/ansible/values.yaml +++ b/installers/ansible/values.yaml @@ -17,7 +17,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos7-13.1-4.5.1" +ccp_image_tag: "centos8-13.1-4.5.1" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -57,7 +57,7 @@ pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos7-4.5.1" +pgo_image_tag: "centos8-4.5.1" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/gcp-marketplace/values.yaml b/installers/gcp-marketplace/values.yaml index c86f34b68a..a90d892520 100644 --- a/installers/gcp-marketplace/values.yaml +++ b/installers/gcp-marketplace/values.yaml @@ -10,7 +10,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos7-13.1-4.5.1" +ccp_image_tag: "centos8-13.1-4.5.1" create_rbac: "true" db_name: "" db_password_age_days: "0" @@ -34,7 +34,7 @@ pgo_client_container_install: "false" pgo_client_install: 'false' pgo_client_version: "4.5.1" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos7-4.5.1" +pgo_image_tag: "centos8-4.5.1" pgo_installation_name: '${OPERATOR_NAME}' pgo_operator_namespace: '${OPERATOR_NAMESPACE}' scheduler_timeout: "3600" diff --git a/installers/helm/values.yaml b/installers/helm/values.yaml index 536a1bcb9d..b3362d98e7 100644 --- a/installers/helm/values.yaml +++ b/installers/helm/values.yaml @@ -37,7 +37,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos7-13.1-4.5.1" +ccp_image_tag: "centos8-13.1-4.5.1" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -77,7 +77,7 @@ pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos7-4.5.1" +pgo_image_tag: "centos8-4.5.1" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/kubectl/postgres-operator-ocp311.yml b/installers/kubectl/postgres-operator-ocp311.yml index f0929e4b14..f2d411b295 100644 --- a/installers/kubectl/postgres-operator-ocp311.yml +++ b/installers/kubectl/postgres-operator-ocp311.yml @@ -44,7 +44,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos7-13.1-4.5.1" + ccp_image_tag: "centos8-13.1-4.5.1" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -84,7 +84,7 @@ data: pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos7-4.5.1" + pgo_image_tag: "centos8-4.5.1" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -161,7 +161,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos7-4.5.1 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.5.1 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/kubectl/postgres-operator.yml b/installers/kubectl/postgres-operator.yml index 91e05d823e..cf48f71d50 100644 --- a/installers/kubectl/postgres-operator.yml +++ b/installers/kubectl/postgres-operator.yml @@ -138,7 +138,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos7-13.1-4.5.1" + ccp_image_tag: "centos8-13.1-4.5.1" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -178,7 +178,7 @@ data: pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos7-4.5.1" + pgo_image_tag: "centos8-4.5.1" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -268,7 +268,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos7-4.5.1 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.5.1 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/helm/helm_template.yaml b/installers/metrics/helm/helm_template.yaml index b328adba55..d0f8e64273 100644 --- a/installers/metrics/helm/helm_template.yaml +++ b/installers/metrics/helm/helm_template.yaml @@ -20,5 +20,5 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos7-4.5.1" +pgo_image_tag: "centos8-4.5.1" diff --git a/installers/metrics/helm/values.yaml b/installers/metrics/helm/values.yaml index 616001b5ec..edb41f9038 100644 --- a/installers/metrics/helm/values.yaml +++ b/installers/metrics/helm/values.yaml @@ -20,7 +20,7 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos7-4.5.1" +pgo_image_tag: "centos8-4.5.1" # ===================== # Configuration Options diff --git a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml index f4643fc126..b9bdd356a3 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml @@ -96,7 +96,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos7-4.5.1 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.5.1 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/kubectl/postgres-operator-metrics.yml b/installers/metrics/kubectl/postgres-operator-metrics.yml index 313698aaeb..87ca1f8af1 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics.yml @@ -165,7 +165,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos7-4.5.1 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.5.1 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/olm/Makefile b/installers/olm/Makefile index 36a11f149e..2650440832 100644 --- a/installers/olm/Makefile +++ b/installers/olm/Makefile @@ -9,7 +9,7 @@ KUBECONFIG ?= $(HOME)/.kube/config OLM_SDK_VERSION ?= 0.15.1 OLM_TOOLS ?= registry.localhost:5000/postgres-operator-olm-tools:$(OLM_SDK_VERSION) OLM_VERSION ?= 0.15.1 -PGO_BASEOS ?= centos7 +PGO_BASEOS ?= centos8 PGO_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata PGO_VERSION ?= 4.5.1 PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) diff --git a/internal/util/util_test.go b/internal/util/util_test.go index d867351a85..08c6966a3a 100644 --- a/internal/util/util_test.go +++ b/internal/util/util_test.go @@ -17,25 +17,25 @@ func TestGetStandardImageTag(t *testing.T) { expected string }{ { - "image: crunchy-postgres-ha, tag: centos7-12.4-4.5.0", + "image: crunchy-postgres-ha, tag: centos8-12.4-4.5.0", "crunchy-postgres-ha", - "centos7-12.4-4.5.0", - "centos7-12.4-4.5.0", + "centos8-12.4-4.5.0", + "centos8-12.4-4.5.0", }, { - "image: crunchy-postgres-gis-ha, tag: centos7-12.4-3.0-4.5.0", + "image: crunchy-postgres-gis-ha, tag: centos8-12.4-3.0-4.5.0", "crunchy-postgres-gis-ha", - "centos7-12.4-3.0-4.5.0", - "centos7-12.4-4.5.0", + "centos8-12.4-3.0-4.5.0", + "centos8-12.4-4.5.0", }, { - "image: crunchy-postgres-ha, tag: centos7-12.4-4.5.0-beta.1", + "image: crunchy-postgres-ha, tag: centos8-12.4-4.5.0-beta.1", "crunchy-postgres-ha", - "centos7-12.4-4.5.0-beta.1", - "centos7-12.4-4.5.0-beta.1", + "centos8-12.4-4.5.0-beta.1", + "centos8-12.4-4.5.0-beta.1", }, { - "image: crunchy-postgres-gis-ha, tag: centos7-12.4-3.0-4.5.0-beta.2", + "image: crunchy-postgres-gis-ha, tag: centos8-12.4-3.0-4.5.0-beta.2", "crunchy-postgres-gis-ha", - "centos7-12.4-3.0-4.5.0-beta.2", - "centos7-12.4-4.5.0-beta.2", + "centos8-12.4-3.0-4.5.0-beta.2", + "centos8-12.4-4.5.0-beta.2", }, { "image: crunchy-postgres-ha, tag: centos8-9.5.23-4.5.0-rc.1", "crunchy-postgres-ha", From 60132e03ac5c882604a175495e3c20a57fd1d715 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 4 Jan 2021 18:10:42 -0500 Subject: [PATCH 118/276] Version bump 4.6.0-beta.1 --- Makefile | 2 +- README.md | 10 +- bin/push-ccp-to-gcr.sh | 2 +- conf/postgres-operator/pgo.yaml | 4 +- docs/config.toml | 2 +- docs/content/Configuration/compatibility.md | 6 + docs/content/_index.md | 9 +- docs/content/releases/4.6.0.md | 233 ++++++++++++++++++ examples/create-by-resource/fromcrd.json | 6 +- examples/envs.sh | 2 +- .../create-cluster/templates/pgcluster.yaml | 2 +- examples/helm/create-cluster/values.yaml | 4 +- examples/kustomize/createcluster/README.md | 16 +- .../createcluster/base/pgcluster.yaml | 6 +- .../overlay/staging/hippo-rpl1-pgreplica.yaml | 2 +- installers/ansible/README.md | 2 +- installers/ansible/values.yaml | 6 +- installers/gcp-marketplace/Makefile | 2 +- installers/gcp-marketplace/README.md | 2 +- installers/gcp-marketplace/values.yaml | 6 +- installers/helm/Chart.yaml | 2 +- installers/helm/values.yaml | 6 +- installers/kubectl/client-setup.sh | 2 +- .../kubectl/postgres-operator-ocp311.yml | 8 +- installers/kubectl/postgres-operator.yml | 8 +- installers/metrics/ansible/README.md | 2 +- installers/metrics/helm/Chart.yaml | 2 +- installers/metrics/helm/helm_template.yaml | 2 +- installers/metrics/helm/values.yaml | 2 +- .../postgres-operator-metrics-ocp311.yml | 2 +- .../kubectl/postgres-operator-metrics.yml | 2 +- installers/olm/Makefile | 2 +- pkg/apis/crunchydata.com/v1/doc.go | 8 +- pkg/apiservermsgs/common.go | 2 +- redhat/atomic/help.1 | 2 +- redhat/atomic/help.md | 2 +- 36 files changed, 312 insertions(+), 66 deletions(-) create mode 100644 docs/content/releases/4.6.0.md diff --git a/Makefile b/Makefile index 1469e405b1..62661ad617 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ PGOROOT ?= $(CURDIR) PGO_BASEOS ?= centos8 PGO_IMAGE_PREFIX ?= crunchydata PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) -PGO_VERSION ?= 4.5.1 +PGO_VERSION ?= 4.6.0-beta.1 PGO_PG_VERSION ?= 12 PGO_PG_FULLVERSION ?= 13.1 PGO_BACKREST_VERSION ?= 2.31 diff --git a/README.md b/README.md index 784fad181e..8051906430 100644 --- a/README.md +++ b/README.md @@ -131,14 +131,18 @@ The PostgreSQL Operator is developed and tested on CentOS and RHEL linux platfor ### Supported Platforms -The Crunchy PostgreSQL Operator is tested on the following Platforms: +The Crunchy PostgreSQL Operator maintains backwards compatibility to Kubernetes 1.11 and is tested is tested against the following Platforms: -- Kubernetes 1.13+ -- OpenShift 3.11+ +- Kubernetes 1.17+ +- Openshift 4.4+ +- OpenShift 3.11 - Google Kubernetes Engine (GKE), including Anthos - Amazon EKS +- Microsoft AKS - VMware Enterprise PKS 1.3+ +This list only includes the platforms that the PostgreSQL Operator is specifically tested on as part of the release process: the PostgreSQL Operator works on other Kubernetes distributions as well. + ### Storage The Crunchy PostgreSQL Operator is tested with a variety of different types of Kubernetes storage and Storage Classes, including: diff --git a/bin/push-ccp-to-gcr.sh b/bin/push-ccp-to-gcr.sh index f1565f0dfb..a235fecf59 100755 --- a/bin/push-ccp-to-gcr.sh +++ b/bin/push-ccp-to-gcr.sh @@ -16,7 +16,7 @@ GCR_IMAGE_PREFIX=gcr.io/crunchy-dev-test CCP_IMAGE_PREFIX=crunchydata -CCP_IMAGE_TAG=centos8-13.1-4.5.1 +CCP_IMAGE_TAG=centos8-13.1-4.6.0-beta.1 IMAGES=( crunchy-prometheus diff --git a/conf/postgres-operator/pgo.yaml b/conf/postgres-operator/pgo.yaml index 0b90394354..2b0de113bf 100644 --- a/conf/postgres-operator/pgo.yaml +++ b/conf/postgres-operator/pgo.yaml @@ -2,7 +2,7 @@ Cluster: CCPImagePrefix: registry.developers.crunchydata.com/crunchydata Metrics: false Badger: false - CCPImageTag: centos8-13.1-4.5.1 + CCPImageTag: centos8-13.1-4.6.0-beta.1 Port: 5432 PGBadgerPort: 10000 ExporterPort: 9187 @@ -81,4 +81,4 @@ Storage: Pgo: Audit: false PGOImagePrefix: registry.developers.crunchydata.com/crunchydata - PGOImageTag: centos8-4.5.1 + PGOImageTag: centos8-4.6.0-beta.1 diff --git a/docs/config.toml b/docs/config.toml index cafb971a4d..3a59d4523e 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -25,7 +25,7 @@ disableNavChevron = false # set true to hide next/prev chevron, default is false highlightClientSide = false # set true to use highlight.pack.js instead of the default hugo chroma highlighter menushortcutsnewtab = true # set true to open shortcuts links to a new tab/window enableGitInfo = true -operatorVersion = "4.5.1" +operatorVersion = "4.6.0-beta.1" postgresVersion = "13.1" postgresVersion13 = "13.1" postgresVersion12 = "13.1" diff --git a/docs/content/Configuration/compatibility.md b/docs/content/Configuration/compatibility.md index 43af7021db..b28901568f 100644 --- a/docs/content/Configuration/compatibility.md +++ b/docs/content/Configuration/compatibility.md @@ -12,6 +12,12 @@ version dependencies between the two projects. Below are the operator releases a | Operator Release | Container Release | Postgres | PgBackrest Version |:----------|:-------------|:------------|:-------------- +| 4.6.0 | 4.6.0 | 13.1 | 2.31 | +|||12.5|2.31| +|||11.10|2.31| +|||10.15|2.31| +|||9.6.20|2.31| +|||| | 4.5.1 | 4.5.1 | 13.1 | 2.29 | |||12.5|2.29| |||11.10|2.29| diff --git a/docs/content/_index.md b/docs/content/_index.md index 96f6807560..50fbc10943 100644 --- a/docs/content/_index.md +++ b/docs/content/_index.md @@ -140,14 +140,17 @@ For more information about which versions of the PostgreSQL Operator include whi # Supported Platforms -The Crunchy PostgreSQL Operator is tested on the following Platforms: +The Crunchy PostgreSQL Operator maintains backwards compatibility to Kubernetes 1.11 and is tested is tested against the following Platforms: -- Kubernetes 1.13+ -- OpenShift 3.11+ +- Kubernetes 1.17+ +- Openshift 4.4+ +- OpenShift 3.11 - Google Kubernetes Engine (GKE), including Anthos - Amazon EKS +- Microsoft AKS - VMware Enterprise PKS 1.3+ +This list only includes the platforms that the PostgreSQL Operator is specifically tested on as part of the release process: the PostgreSQL Operator works on other Kubernetes distributions as well. ## Storage The Crunchy PostgreSQL Operator is tested with a variety of different types of Kubernetes storage and Storage Classes, including: diff --git a/docs/content/releases/4.6.0.md b/docs/content/releases/4.6.0.md new file mode 100644 index 0000000000..d7719433cf --- /dev/null +++ b/docs/content/releases/4.6.0.md @@ -0,0 +1,233 @@ +--- +title: "4.6.0" +date: +draft: false +weight: 60 +--- + +Crunchy Data announces the release of the PostgreSQL Operator 4.6.0 on January DD, 2021. You can get started with the PostgreSQL Operator with the following commands: + +``` +kubectl create namespace pgo +kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.0-beta.1/installers/kubectl/postgres-operator.yml +``` + +The PostgreSQL Operator is released in conjunction with the [Crunchy Container Suite](https://github.com/CrunchyData/crunchy-containers/). + +The PostgreSQL Operator 4.6.0 release includes the following software versions upgrades: + +- [pgBackRest](https://pgbackrest.org/) is now at version 2.31. +- [pgnodemx](https://github.com/CrunchyData/pgnodemx) is now at version 1.0.3 +- [Patroni](https://patroni.readthedocs.io/) is now at version 2.0.1 + +The monitoring stack for the PostgreSQL Operator uses upstream components as opposed to repackaging them. These are specified as part of the [PostgreSQL Operator Installer](https://access.crunchydata.com/documentation/postgres-operator/latest/installation/postgres-operator/). We have tested this release with the following versions of each component: + +- Prometheus: 2.23.0 +- Grafana: 6.7.5 +- Alertmanager: 0.21.0 + +This release of the PostgreSQL Operator drops support for PostgreSQL 9.5, which goes EOL in February 2021. + +PostgreSQL Operator is tested against Kubernetes 1.17 - 1.20, OpenShift 3.11, OpenShift 4.4+, Google Kubernetes Engine (GKE), Amazon EKS, Microsoft AKS, and VMware Enterprise PKS 1.3+, and works on other Kubernetes distributions as well. + +## Major Features + +### Rolling Updates + +During the lifecycle of a PostgreSQL cluster, there are certain events that may require a planned restart, such as an update to a "restart required" PostgreSQL configuration setting (e.g. [`shared_buffers`](https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-SHARED-BUFFERS)) or a change to a Kubernetes Deployment template (e.g. [changing the memory request](https://access.crunchydata.com/documentation/postgres-operator/latest/tutorial/customize-cluster/#customize-cpu-memory)). Restarts can be disruptive in a high availability deployment, which is why many setups employ a ["rolling update" strategy](https://kubernetes.io/docs/tutorials/kubernetes-basics/update/update-intro/) (aka a "rolling restart") to minimize or eliminate downtime during a planned restart. + +Because PostgreSQL is a stateful application, a simple rolling restart strategy will not work: PostgreSQL needs to ensure that there is a primary available that can accept reads and writes. This requires following a method that will minimize the amount of downtime when the primary is taken offline for a restart. + +This release introduces a mechanism for the PostgreSQL Operator to perform rolling updates implicitly on certain operations that change the Deployment templates and explicitly through the [`pgo restart`](https://access.crunchydata.com/documentation/postgres-operator/latest/pgo-client/reference/pgo_restart/) command with the `--rolling` flag. Some of the operations that will trigger a rolling update include: + +- Memory resource adjustments +- CPU resource adjustments +- Custom annotation changes +- Tablespace additions +- Adding/removing the metrics sidecar to a PostgreSQL cluster + +Please reference the [documentation](https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/high-availability/#rolling-updates) for more details on [rolling updates](https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/high-availability/#rolling-updates). + +### Pod Tolerations + +Kubernetes [Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) can help with the scheduling of Pods to appropriate Nodes based upon the taint values of said Nodes. For example, a Kubernetes administrator may set taints on Nodes to restrict scheduling to just the database workload, and as such, tolerations must be assigned to Pods to ensure they can actually be scheduled on thos nodes. + +This release introduces the ability to assign tolerations to PostgreSQL clusters managed by the PostgreSQL Operator. Tolerations can be assigned to every instance in the cluster via the `tolerations` attribute on a `pgclusters.crunchydata.com` custom resource, or to individual instances using the `tolerations` attribute on a `pgreplicas.crunchydata.com` custom resource. + +Both the [`pgo create cluster`](https://access.crunchydata.com/documentation/postgres-operator/latest/pgo-client/reference/pgo_create_cluster/) and [`pgo scale`](https://access.crunchydata.com/documentation/postgres-operator/latest/pgo-client/reference/pgo_scale/) commands support the `--toleration` flag, which can be used to add one or more tolerations to a cluster. Values accepted by the `--toleration` flag use the following format: + +``` +rule:Effect +``` + +where a `rule` can represent existence (e.g. `key`) or equality (`key=value`) and `Effect` is one of `NoSchedule`, `PreferNoSchedule`, or `NoExecute`, e.g: + +``` +pgo create cluster hippo \ + --toleration=ssd:NoSchedule \ + --toleration=zone=east:NoSchedule +``` + +Tolerations can also be added and removed from an existing cluster using the [`pgo update cluster`](https://access.crunchydata.com/documentation/postgres-operator/latest/pgo-client/reference/pgo_update_cluster/) , command e.g: + +``` +pgo update cluster hippo \ + --toleration=zone=west:NoSchedule \ + --toleration=zone=east:NoSchedule- +``` + +or by modifying the `pgclusters.crunchydata.com` custom resource directly. + +For more information on how tolerations work, please refer to the [Kubernetes documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/). + +### Node Affinity Enhancements + +Node affinity has been a feature of the PostgreSQL Operator for a long time but has received some significant improvements in this release. + +It is now possible to control the node affinity across an entire PostgreSQL cluster as well as individual PostgreSQL instances from a custom resource attribute on the `pgclusters.crunchydata.com` and `pgreplicas.crunchydata.com` CRDs. These attributes use the standard [Kubernetes specifications for node affinity](https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/) and should be familiar to users who have had to set this in applications. + +Additionally, this release adds support for both "preferred" and "required" node affinity definitions. Previously, one could achieve required node affinity by modifying a template in the `pgo-config` ConfigMap, but this release makes this process more straightforward. + +This release introduces the `--node-affinity-type` flag for the `pgo create cluster`, `pgo scale`, and `pgo restore` commands that allows one to specify the node affinity type for PostgreSQL clusters and instances. The `--node-affinity-type` flag accepts values of `preferred` (default) and `required`. Each instance in a PostgreSQL cluster will inherit its node affinity type from the cluster (`pgo create cluster`) itself, but the type of an individual instance (`pgo scale`) will supersede that value. + +The `--node-affinity-type` must be combined with the `--node-label` flag. + +### TLS for pgBouncer + +Since 4.3.0, the PostgreSQL Operator has had support for [TLS connections to PostgreSQL clusters](https://access.crunchydata.com/documentation/postgres-operator/latest/tutorial/tls/) and an [improved integration with pgBouncer](https://access.crunchydata.com/documentation/postgres-operator/latest/tutorial/pgbouncer/), used for connection pooling and state management. However, the integration with pgBouncer did not support TLS directly: it could be achieved through modifying the pgBouncer Deployment template. + +This release brings TLS support for pgBouncer to the PostgreSQL Operator, allowing for communication over TLS between a client and pgBouncer, and pgBouncer and a PostgreSQL server. In other words, the following is now support: + +`Client` <= TLS => `pgBouncer` <= TLS => `PostgreSQL` + +In other words, to use TLS with pgBouncer, all connections from a client to pgBouncer and from pgBouncer to PostgreSQL **must** be over TLS. Effectively, this is "TLS only" mode if connecting via pgBouncer. + +In order to deploy pgBouncer with TLS, the following preconditions must be met: + +- TLS **MUST** be enabled within the PostgreSQL cluster. +- pgBouncer and the PostgreSQL **MUST** share the same certificate authority (CA) bundle. + +You must have a [Kubernetes TLS Secret](https://kubernetes.io/docs/concepts/configuration/secret/#tls-secrets) containing the TLS keypair you would like to use for pgBouncer. + +You can enable TLS for pgBouncer using the following commands: + +- `pgo create pgbouncer --tls-secret`, where `--tls-secret` specifies the location of the TLS keypair to use for pgBouncer. You **must** already have TLS enabled in your PostgreSQL cluster. +- `pgo create cluster --pgbouncer --pgbouncer-tls-secret`, where `--tls-secret` specifies the location of the TLS keypair to use for pgBouncer. You **must** also specify `--server-tls-secret` and `--server-ca-secret`. + +This adds an attribute to the `pgclusters.crunchydata.com` Customer Resource Definition in the `pgBouncer` section called `tlsSecret`, which will store the name of the TLS secret to use for pgBouncer. + +By default, connections coming into pgBouncer have a [PostgreSQL SSL mode](https://www.postgresql.org/docs/current/libpq-ssl.html#LIBPQ-SSL-PROTECTION) of `require` and connections going into PostgreSQL using `verify-ca`. + +### Enable/Disable Metrics Collection for PostgreSQL Cluster + +A common case is that one creates a PostgreSQL cluster with the Postgres Operator and forget to enable it for monitoring with the `--metrics` flag. Prior to this release, adding the `crunchy-postgres-exporter` to an already running PostgreSQL cluster presented challenges. + +This release brings the `--enable-metrics` and `--disable-metrics` introduces to the [`pgo update cluster`](https://access.crunchydata.com/documentation/postgres-operator/latest/pgo-client/reference/pgo_update_cluster/) flags that allow for monitoring to be enabled or disabled on an already running PostgreSQL cluster. As this involves modifying Deployment templates, this action triggers a rolling update that is described in the previous section to limit downtime. + +Metrics can also be enabled/disabled using the `exporter` attribute on the `pgclusters.crunchydata.com` custom resource. + +This release also changes the management of the PostgreSQL user that is used to collect the metrics. Similar to [pgBouncer](https://access.crunchydata.com/documentation/postgres-operator/latest/tutorial/pgbouncer/), the PostgreSQL Operator fully manages the credentials for the metrics collection user. The `--exporter-rotate-password` flag on [`pgo update cluster`](https://access.crunchydata.com/documentation/postgres-operator/latest/pgo-client/reference/pgo_update_cluster/) can be used to rotate the metric collection user's credentials. + +## Container Image Reduction & Reorganization + +Advances in Postgres Operator functionality have allowed for a culling of the number of required container images. For example, functionality that had been broken out into individual container images (e.g. `crunchy-pgdump`) is now consolidated within the `crunchy-postgres` and `crunchy-postgres-ha` containers. + +Renamed container images include: + +- `pgo-backrest` => `crunchy-pgbackrest` +- `pgo-backrest-repo` => `crunchy-pgbackrest-repo` + +Removed container images include: + +- `crunchy-admin` +- `crunchy-backrest-restore` +- `crunchy-backup` +- `crunchy-pgbench` +- `crunchy-pgdump` +- `crunchy-pgrestore` +- `pgo-sqlrunner` +- `pgo-backrest-repo-sync` +- `pgo-backrest-restore` + +These changes also include overall organization and build performance optimizations around the container suite. + +## Breaking Changes + +- [Metrics collection](https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/monitoring/) can now be enabled/disabled using the `exporter` attribute on `pgclusters.crunchydata.com`. The previous method to do so, involving a label buried within a custom resource, no longer works. +- pgBadger can now be enabled/disabled using the `pgBadger` attribute on `pgclusters.crunchydata.com`. The previous method to do so, involving a label buried within a custom resource, no longer works. +- Several additional labels on the `pgclusters.crunchydata.com` CRD that had driven behavior have been moved to attributes. These include: + - `autofail`, which is now represented by the `disableAutofail` attribute. + - `service-type`, which is now represented by the `serviceType` attribute. + - `NodeLabelKey`/`NodeLabelValue`, which is now replaced by the `nodeAffinity` attribute. + - `backrest-storage-type`, which is now represented with the `backrestStorageTypes` attribute. +The `pgo upgrade` command will properly moved any data you have in these labels into the correct attributes. You can read more about how to use the various CRD attributes in the [Custom Resources](https://access.crunchydata.com/documentation/postgres-operator/latest/custom-resources/) section of the documentation. +- The `rootsecretname`, `primarysecretname`, and `usersecretname` attributes on the `pgclusters.crunchydata.com` CRD have been removed. Each of these represented managed Secrets. Additionally, if the managed Secrets are not created at cluster creation time, the Operator will now generate these Secrets. +- The `collectSecretName` attribute on `pgclusters.crunchydata.com` has been removed. The Secret for the metrics collection user is now fully managed by the PostgreSQL Operator. +- There are changes to the `exporter.json` and `cluster-deployment.json` templates that reside within the `pgo-config` ConfigMap that could be breaking to those who have customized those templates. This includes removing the opening comma in the `exporter.json` and removing unneeded match labels on the PostgreSQL cluster Deployment. This is resolved by following the [standard upgrade procedure](https://access.crunchydata.com/documentation/postgres-operator/latest/upgrade/).(https://access.crunchydata.com/documentation/postgres-operator/latest/upgrade/), and only affects new clusters and existing clusters that wish to use the enable/disable metric collection feature. +The `affinity.json` entry in the `pgo-config` ConfigMap has been removed in favor of the updated node affinity support. +- Failovers can no longer be controlled by creating a `pgtasks.crunchydata.com` custom resource. +- Remove the `PgMonitorPassword` attribute from `pgo-deployer`. The metric collection user password is managed by the PostgreSQL Operator. +- Policy creation only supports the method of creating the policy from a file/ConfigMap. +- Any pgBackRest variables of the format `PGBACKREST_REPO_` now follow the format `PGBACKREST_REPO1_` to be consistent with what pgBackRest expects. + +## Features + +- [Monitoring](https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/monitoring/) can now be enabled/disabled during the lifetime of a PostgreSQL cluster using the `pgo update --enable-metrics` and `pgo update --disable-metrics` flag. This can also be modified directly on a custom resource. +- The Service Type of a PostgreSQL cluster can now be updated during the lifetime of a cluster with `pgo update cluster --service-type`. This can also be modified directly on a custom resource. +- The Service Type of pgBouncer can now be independently controlled and set with the `--service-type` flag on `pgo create pgbouncer` and `pgo update pgbouncer`. This can also be modified directly on a custom resource. +- [pgBackRest delta restores](https://pgbackrest.org/user-guide.html#restore/option-delta), which can efficiently restore data as it determines which specific files need to be restored from backup, can now be used as part of the cluster creation method with `pgo create cluster --restore-from`. For example, if a cluster is deleted as such: + +``` +pgo delete cluster hippo --keep-data --keep-backups +``` + +It can subsequently be recreated using the delta restore method as such: + +``` +pgo create cluster hippo --restore-from=hippo +``` + +Passing in the [`--process-max`](https://pgbackrest.org/command.html#command-archive-get/category-general/option-process-max) option to `--restore-opts` can help speed up the restore process based upon the amount of CPU you have available. If the delta restore fails, the PostgreSQL Operator will attempt to perform a full restore. + +- `pgo restore` will now first attempt a [pgBackRest delta restore](https://pgbackrest.org/user-guide.html#restore/option-delta), which can significantly speed up the restore time for large databases. Passing in the [`--process-max`](https://pgbackrest.org/command.html#command-archive-get/category-general/option-process-max) option to `--backup-opts` can help speed up the restore process based upon the amount of CPU you have available. +- A pgBackRest backup can now be deleted with `pgo delete backup`. A backup name must be specified with the `--target` flag. Please refer to the [documentation](https://access.crunchydata.com/documentation/postgres-operator/latest/tutorial/disaster-recovery/#deleting-a-backup) for how to use this command. +- pgBadger can now be enabled/disabled during the lifetime of a PostgreSQL cluster using the `pgo update --enable-pgbadger` and `pgo update --disable-pgbadger` flag. This can also be modified directly on a custom resource. + +## Changes + +- If not provided at installation time, the Operator will now generate its own `pgo-backrest-repo-config` Secret. +- The `local` storage type option for pgBackRest is deprecated in favor of `posix`, which matches the pgBackRest term. `local` will still continue to work for backwards compatibility purposes. +- PostgreSQL clusters using multi-repository (e.g. `posix` + `s3` at the same time) archiving will now, by default, take backups to both repositories when `pgo backup` is used without additional options. +- If not provided a cluster creation time, the Operator will now generate the PostgreSQL user Secrets required for bootstrap, including the superuser (`postgres`), the replication user (`primaryuser`), and the standard user. +- `crunchy-postgres-exporter` now exposes several pgMonitor metrics related to `pg_stat_statements`. +- When using the `--restore-from` option on `pgo create cluster` to create a new PostgreSQL cluster, the cluster bootstrap Job is now automatically removed if it completes successfully. +- The `pgo failover` command now works without specifying a target: the candidate to fail over to will be automatically selected. +- For clusters that have no healthy instances, `pgo failover` can now force a promotion using the `--force` flag. A `--target` flag must also be specified when using `--force`. +- If a predefined custom ConfigMap for a PostgreSQL cluster (`-pgha-config`) is detected at bootstrap time, the Operator will ensure it properly initializes the cluster. +- PostgreSQL JIT compilation is explicitly disabled on new cluster creation. This prevents a memory leak that has been observed on queries coming from the metrics exporter. +- The credentials for the metrics collection user are now available with `pgo show user --show-system-accounts`. +- The default user for executing scheduled SQL policies is now the Postgres superuser, instead of the replication user. +- Add the `--no-prompt` flag to `pgo upgrade`. The mechanism to disable the prompt verification was already in place, but the flag was not exposed. Reported by (@devopsevd). +- Remove certain characters that causes issues in shell environments from consideration when using the random password generator, which is used to create default passwords or with `--rotate-password`. +- Remove the long deprecated `archivestorage` attribute from the `pgclusters.crunchydata.com` custom resource definition. As this attribute is not used at all, this should have no effect. +- The `ArchiveMode` parameter is now removed from the configuration. This had been fully deprecated for awhile. +- New PostgreSQL Operator deployments will now generate ECDSA keys (P-256, SHA384) for use by the API server. + +## Fixes + +- Ensure custom annotations are applied if the annotations are supposed to be applied globally but the cluster does not have a pgBouncer Deployment. +- Fix issue with UBI 8 / CentOS 8 when running a pgBackRest bootstrap or restore job, where duplicate "repo types" could be set. Specifically, the ensures the name of the repo type is set via the `PGBACKREST_REPO1_TYPE` environmental variable. Reported by Alec Rooney (@alrooney). +- Fix issue where `pgo test` would indicate every Service was a replica if the cluster name contained the word `replica` in it. Reported by Jose Joye (@jose-joye). +- Do not consider Evicted Pods as part of `pgo test`. This eliminates a behavior where faux primaries are considered as part of `pgo test`. Reported by Dennis Jacobfeuerborn (@dennisjac). +- Fix `pgo df` to not fail in the event it tries to execute a command within a dangling container from the bootstrap process when `pgo create cluster --restore-from` is used. Reported by Ignacio J.Ortega (@IJOL). +- `pgo df` will now only attempt to execute in running Pods, i.e. it does not attempt to run in evicted Pods. Reported by (@kseswar). +- Ensure the sync replication ConfigMap is removed when a cluster is deleted. +- Fix crash in shutdown logic when attempting to shut down a cluster where no primaries exist. Reported by Jeffrey den Drijver (@JeffreyDD). +- Fix syntax in recovery check command which could lead to failures when manually promoting a standby cluster. Reported by (@SockenSalat). +- Fix potential race condition that could lead to a crash in the Operator boot when an error is issued around loading the `pgo-config` ConfigMap. Reported by Aleksander Roszig (@AleksanderRoszig). +- Do not trigger a backup if a standby cluster fails over. Reported by (@aprilito1965). +- Remove legacy `defaultMode` setting on the volume instructions for the pgBackRest repo Secret as the `readOnly` setting is used on the mount itself. Reported by (@szhang1). +- The logger no longer defaults to using a log level of `DEBUG`. +- Autofailover is no longer disabled when an `rmdata` Job is run, enabling a clean database shutdown process when deleting a PostgreSQL cluster. +- Major upgrade container now includes references for `pgnodemx`. +- During a major upgrade, ensure permissions are correct on the old data directory before running `pg_upgrade`. diff --git a/examples/create-by-resource/fromcrd.json b/examples/create-by-resource/fromcrd.json index aca1a509c1..dc399d2fd0 100644 --- a/examples/create-by-resource/fromcrd.json +++ b/examples/create-by-resource/fromcrd.json @@ -10,7 +10,7 @@ "deployment-name": "fromcrd", "name": "fromcrd", "pg-cluster": "fromcrd", - "pgo-version": "4.5.1", + "pgo-version": "4.6.0-beta.1", "pgouser": "pgoadmin" }, "name": "fromcrd", @@ -45,7 +45,7 @@ "supplementalgroups": "" }, "ccpimage": "crunchy-postgres-ha", - "ccpimagetag": "centos8-13.1-4.5.1", + "ccpimagetag": "centos8-13.1-4.6.0-beta.1", "clustername": "fromcrd", "database": "userdb", "exporterport": "9187", @@ -60,7 +60,7 @@ "port": "5432", "user": "testuser", "userlabels": { - "pgo-version": "4.5.1" + "pgo-version": "4.6.0-beta.1" } } } diff --git a/examples/envs.sh b/examples/envs.sh index b824de2eeb..1811e2d5f7 100644 --- a/examples/envs.sh +++ b/examples/envs.sh @@ -20,7 +20,7 @@ export PGO_CONF_DIR=$PGOROOT/installers/ansible/roles/pgo-operator/files # the version of the Operator you run is set by these vars export PGO_IMAGE_PREFIX=registry.developers.crunchydata.com/crunchydata export PGO_BASEOS=centos8 -export PGO_VERSION=4.5.1 +export PGO_VERSION=4.6.0-beta.1 export PGO_IMAGE_TAG=$PGO_BASEOS-$PGO_VERSION # for setting the pgo apiserver port, disabling TLS or not verifying TLS diff --git a/examples/helm/create-cluster/templates/pgcluster.yaml b/examples/helm/create-cluster/templates/pgcluster.yaml index 9d1036581d..d137aeef5b 100644 --- a/examples/helm/create-cluster/templates/pgcluster.yaml +++ b/examples/helm/create-cluster/templates/pgcluster.yaml @@ -10,7 +10,7 @@ metadata: deployment-name: {{ .Values.pgclustername }} name: {{ .Values.pgclustername }} pg-cluster: {{ .Values.pgclustername }} - pgo-version: 4.5.1 + pgo-version: 4.6.0-beta.1 pgouser: admin name: {{ .Values.pgclustername }} namespace: {{ .Values.namespace }} diff --git a/examples/helm/create-cluster/values.yaml b/examples/helm/create-cluster/values.yaml index ce2abbf974..af38177a81 100644 --- a/examples/helm/create-cluster/values.yaml +++ b/examples/helm/create-cluster/values.yaml @@ -4,11 +4,11 @@ # The values is for the namespace and the postgresql cluster name ccpimage: crunchy-postgres-ha ccpimageprefix: registry.developers.crunchydata.com/crunchydata -ccpimagetag: centos8-13.1-4.5.1 +ccpimagetag: centos8-13.1-4.6.0-beta.1 namespace: pgo pgclustername: hippo pgoimageprefix: registry.developers.crunchydata.com/crunchydata -pgoversion: 4.5.1 +pgoversion: 4.6.0-beta.1 hipposecretuser: "hippo" hipposecretpassword: "Supersecurepassword*" postgressecretuser: "postgres" diff --git a/examples/kustomize/createcluster/README.md b/examples/kustomize/createcluster/README.md index 6eb934577b..1b03c207e5 100644 --- a/examples/kustomize/createcluster/README.md +++ b/examples/kustomize/createcluster/README.md @@ -44,13 +44,13 @@ pgo show cluster hippo -n pgo ``` You will see something like this if successful: ``` -cluster : hippo (crunchy-postgres-ha:centos8-13.1-4.5.1) +cluster : hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.1) pod : hippo-8fb6bd96-j87wq (Running) on gke-xxxx-default-pool-38e946bd-257w (1/1) (primary) pvc: hippo (1Gi) deployment : hippo deployment : hippo-backrest-shared-repo service : hippo - ClusterIP (10.0.56.86) - Ports (2022/TCP, 5432/TCP) - labels : pgo-version=4.5.1 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.0-beta.1 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata ``` Feel free to run other pgo cli commands on the hippo cluster @@ -79,7 +79,7 @@ pgo show cluster dev-hippo -n pgo ``` You will see something like this if successful: ``` -cluster : dev-hippo (crunchy-postgres-ha:centos8-13.1-4.5.1) +cluster : dev-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.1) pod : dev-hippo-588d4cb746-bwrxb (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: dev-hippo (1Gi) deployment : dev-hippo @@ -87,7 +87,7 @@ cluster : dev-hippo (crunchy-postgres-ha:centos8-13.1-4.5.1) deployment : dev-hippo-pgbouncer service : dev-hippo - ClusterIP (10.0.62.87) - Ports (2022/TCP, 5432/TCP) service : dev-hippo-pgbouncer - ClusterIP (10.0.48.120) - Ports (5432/TCP) - labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.5.1 pgouser=admin + labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.6.0-beta.1 pgouser=admin ``` #### staging The staging overlay will deploy a crunchy postgreSQL cluster with 2 replica's with annotations added @@ -113,7 +113,7 @@ pgo show cluster staging-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : staging-hippo (crunchy-postgres-ha:centos8-13.1-4.5.1) +cluster : staging-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.1) pod : staging-hippo-85cf6dcb65-9h748 (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: staging-hippo (1Gi) pod : staging-hippo-lnxw-cf47d8c8b-6r4wn (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (replica) @@ -128,7 +128,7 @@ cluster : staging-hippo (crunchy-postgres-ha:centos8-13.1-4.5.1) service : staging-hippo-replica - ClusterIP (10.0.56.57) - Ports (2022/TCP, 5432/TCP) pgreplica : staging-hippo-lnxw pgreplica : staging-hippo-rpl1 - labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.5.1 pgouser=admin vendor=crunchydata + labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.6.0-beta.1 pgouser=admin vendor=crunchydata ``` #### production @@ -154,7 +154,7 @@ pgo show cluster prod-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : prod-hippo (crunchy-postgres-ha:centos8-13.1-4.5.1) +cluster : prod-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.1) pod : prod-hippo-5d6dd46497-rr67c (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (primary) pvc: prod-hippo (1Gi) pod : prod-hippo-flty-84d97c8769-2pzbh (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (replica) @@ -165,7 +165,7 @@ cluster : prod-hippo (crunchy-postgres-ha:centos8-13.1-4.5.1) service : prod-hippo - ClusterIP (10.0.56.18) - Ports (2022/TCP, 5432/TCP) service : prod-hippo-replica - ClusterIP (10.0.56.101) - Ports (2022/TCP, 5432/TCP) pgreplica : prod-hippo-flty - labels : pgo-version=4.5.1 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.0-beta.1 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata ``` ### Delete the clusters To delete the clusters run the following pgo cli commands diff --git a/examples/kustomize/createcluster/base/pgcluster.yaml b/examples/kustomize/createcluster/base/pgcluster.yaml index f070683e08..91c0ad713c 100644 --- a/examples/kustomize/createcluster/base/pgcluster.yaml +++ b/examples/kustomize/createcluster/base/pgcluster.yaml @@ -10,7 +10,7 @@ metadata: deployment-name: hippo name: hippo pg-cluster: hippo - pgo-version: 4.5.1 + pgo-version: 4.6.0-beta.1 pgouser: admin name: hippo namespace: pgo @@ -42,7 +42,7 @@ spec: annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: registry.developers.crunchydata.com/crunchydata - ccpimagetag: centos8-13.1-4.5.1 + ccpimagetag: centos8-13.1-4.6.0-beta.1 clustername: hippo customconfig: "" database: hippo @@ -63,4 +63,4 @@ spec: port: "5432" user: hippo userlabels: - pgo-version: 4.5.1 + pgo-version: 4.6.0-beta.1 diff --git a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml index 253995eb69..c371817730 100644 --- a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml +++ b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml @@ -20,4 +20,4 @@ spec: storagetype: dynamic supplementalgroups: "" userlabels: - pgo-version: 4.5.1 + pgo-version: 4.6.0-beta.1 diff --git a/installers/ansible/README.md b/installers/ansible/README.md index 345d035037..b82f698d2a 100644 --- a/installers/ansible/README.md +++ b/installers/ansible/README.md @@ -4,7 +4,7 @@ Crunchy Data

-Latest Release: 4.5.1 +Latest Release: 4.6.0-beta.1 ## General diff --git a/installers/ansible/values.yaml b/installers/ansible/values.yaml index 2675ac8eb6..c8ee2b641a 100644 --- a/installers/ansible/values.yaml +++ b/installers/ansible/values.yaml @@ -17,7 +17,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.1-4.5.1" +ccp_image_tag: "centos8-13.1-4.6.0-beta.1" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -50,14 +50,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.5.1" +pgo_client_version: "4.6.0-beta.1" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos8-4.5.1" +pgo_image_tag: "centos8-4.6.0-beta.1" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/gcp-marketplace/Makefile b/installers/gcp-marketplace/Makefile index 6236ae3ad8..ac2e205f8b 100644 --- a/installers/gcp-marketplace/Makefile +++ b/installers/gcp-marketplace/Makefile @@ -6,7 +6,7 @@ MARKETPLACE_TOOLS ?= gcr.io/cloud-marketplace-tools/k8s/dev:$(MARKETPLACE_VERSIO MARKETPLACE_VERSION ?= 0.9.4 KUBECONFIG ?= $(HOME)/.kube/config PARAMETERS ?= {} -PGO_VERSION ?= 4.5.1 +PGO_VERSION ?= 4.6.0-beta.1 IMAGE_BUILD_ARGS = --build-arg MARKETPLACE_VERSION='$(MARKETPLACE_VERSION)' \ --build-arg PGO_VERSION='$(PGO_VERSION)' diff --git a/installers/gcp-marketplace/README.md b/installers/gcp-marketplace/README.md index af2e60f80c..cd71a9b719 100644 --- a/installers/gcp-marketplace/README.md +++ b/installers/gcp-marketplace/README.md @@ -59,7 +59,7 @@ Google Cloud Marketplace. ```shell IMAGE_REPOSITORY=gcr.io/crunchydata-public/postgres-operator - export PGO_VERSION=4.5.1 + export PGO_VERSION=4.6.0-beta.1 export INSTALLER_IMAGE=${IMAGE_REPOSITORY}/deployer:${PGO_VERSION} export OPERATOR_IMAGE=${IMAGE_REPOSITORY}:${PGO_VERSION} export OPERATOR_IMAGE_API=${IMAGE_REPOSITORY}/pgo-apiserver:${PGO_VERSION} diff --git a/installers/gcp-marketplace/values.yaml b/installers/gcp-marketplace/values.yaml index a90d892520..3be2c2e119 100644 --- a/installers/gcp-marketplace/values.yaml +++ b/installers/gcp-marketplace/values.yaml @@ -10,7 +10,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.1-4.5.1" +ccp_image_tag: "centos8-13.1-4.6.0-beta.1" create_rbac: "true" db_name: "" db_password_age_days: "0" @@ -32,9 +32,9 @@ pgo_admin_role_name: "pgoadmin" pgo_admin_username: "admin" pgo_client_container_install: "false" pgo_client_install: 'false' -pgo_client_version: "4.5.1" +pgo_client_version: "4.6.0-beta.1" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.5.1" +pgo_image_tag: "centos8-4.6.0-beta.1" pgo_installation_name: '${OPERATOR_NAME}' pgo_operator_namespace: '${OPERATOR_NAMESPACE}' scheduler_timeout: "3600" diff --git a/installers/helm/Chart.yaml b/installers/helm/Chart.yaml index e7a55444cb..a597693462 100644 --- a/installers/helm/Chart.yaml +++ b/installers/helm/Chart.yaml @@ -3,7 +3,7 @@ name: postgres-operator description: Crunchy PostgreSQL Operator Helm chart for Kubernetes type: application version: 0.1.0 -appVersion: 4.5.1 +appVersion: 4.6.0-beta.1 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/crunchy_logo.png keywords: diff --git a/installers/helm/values.yaml b/installers/helm/values.yaml index b3362d98e7..272b6f5ebc 100644 --- a/installers/helm/values.yaml +++ b/installers/helm/values.yaml @@ -37,7 +37,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.1-4.5.1" +ccp_image_tag: "centos8-13.1-4.6.0-beta.1" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -70,14 +70,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.5.1" +pgo_client_version: "4.6.0-beta.1" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos8-4.5.1" +pgo_image_tag: "centos8-4.6.0-beta.1" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/kubectl/client-setup.sh b/installers/kubectl/client-setup.sh index 1504009506..a98b99de5b 100755 --- a/installers/kubectl/client-setup.sh +++ b/installers/kubectl/client-setup.sh @@ -14,7 +14,7 @@ # This script should be run after the operator has been deployed PGO_OPERATOR_NAMESPACE="${PGO_OPERATOR_NAMESPACE:-pgo}" PGO_USER_ADMIN="${PGO_USER_ADMIN:-pgouser-admin}" -PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.5.1}" +PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.0-beta.1}" PGO_CLIENT_URL="https://github.com/CrunchyData/postgres-operator/releases/download/${PGO_CLIENT_VERSION}" PGO_CMD="${PGO_CMD-kubectl}" diff --git a/installers/kubectl/postgres-operator-ocp311.yml b/installers/kubectl/postgres-operator-ocp311.yml index f2d411b295..cb1b16c96d 100644 --- a/installers/kubectl/postgres-operator-ocp311.yml +++ b/installers/kubectl/postgres-operator-ocp311.yml @@ -44,7 +44,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos8-13.1-4.5.1" + ccp_image_tag: "centos8-13.1-4.6.0-beta.1" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -77,14 +77,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.5.1" + pgo_client_version: "4.6.0-beta.1" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos8-4.5.1" + pgo_image_tag: "centos8-4.6.0-beta.1" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -161,7 +161,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.5.1 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-beta.1 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/kubectl/postgres-operator.yml b/installers/kubectl/postgres-operator.yml index cf48f71d50..f5a7c18178 100644 --- a/installers/kubectl/postgres-operator.yml +++ b/installers/kubectl/postgres-operator.yml @@ -138,7 +138,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos8-13.1-4.5.1" + ccp_image_tag: "centos8-13.1-4.6.0-beta.1" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -171,14 +171,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.5.1" + pgo_client_version: "4.6.0-beta.1" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos8-4.5.1" + pgo_image_tag: "centos8-4.6.0-beta.1" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -268,7 +268,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.5.1 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-beta.1 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/ansible/README.md b/installers/metrics/ansible/README.md index 57f68cd878..1be5088c15 100644 --- a/installers/metrics/ansible/README.md +++ b/installers/metrics/ansible/README.md @@ -4,7 +4,7 @@ Crunchy Data

-Latest Release: 4.5.1 +Latest Release: 4.6.0-beta.1 ## General diff --git a/installers/metrics/helm/Chart.yaml b/installers/metrics/helm/Chart.yaml index 520204c2d1..a368325252 100644 --- a/installers/metrics/helm/Chart.yaml +++ b/installers/metrics/helm/Chart.yaml @@ -3,6 +3,6 @@ name: postgres-operator-monitoring description: Install for Crunchy PostgreSQL Operator Monitoring type: application version: 0.1.0 -appVersion: 4.5.1 +appVersion: 4.6.0-beta.1 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/crunchy_logo.png \ No newline at end of file diff --git a/installers/metrics/helm/helm_template.yaml b/installers/metrics/helm/helm_template.yaml index d0f8e64273..9cef2423b4 100644 --- a/installers/metrics/helm/helm_template.yaml +++ b/installers/metrics/helm/helm_template.yaml @@ -20,5 +20,5 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.5.1" +pgo_image_tag: "centos8-4.6.0-beta.1" diff --git a/installers/metrics/helm/values.yaml b/installers/metrics/helm/values.yaml index edb41f9038..3baa0c0c04 100644 --- a/installers/metrics/helm/values.yaml +++ b/installers/metrics/helm/values.yaml @@ -20,7 +20,7 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.5.1" +pgo_image_tag: "centos8-4.6.0-beta.1" # ===================== # Configuration Options diff --git a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml index b9bdd356a3..984fb0f6d1 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml @@ -96,7 +96,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.5.1 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-beta.1 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/kubectl/postgres-operator-metrics.yml b/installers/metrics/kubectl/postgres-operator-metrics.yml index 87ca1f8af1..d005d00200 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics.yml @@ -165,7 +165,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.5.1 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-beta.1 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/olm/Makefile b/installers/olm/Makefile index 2650440832..a5f7116d43 100644 --- a/installers/olm/Makefile +++ b/installers/olm/Makefile @@ -11,7 +11,7 @@ OLM_TOOLS ?= registry.localhost:5000/postgres-operator-olm-tools:$(OLM_SDK_VERSI OLM_VERSION ?= 0.15.1 PGO_BASEOS ?= centos8 PGO_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -PGO_VERSION ?= 4.5.1 +PGO_VERSION ?= 4.6.0-beta.1 PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) CCP_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(PGO_VERSION) CCP_POSTGIS_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(CCP_POSTGIS_VERSION)-$(PGO_VERSION) diff --git a/pkg/apis/crunchydata.com/v1/doc.go b/pkg/apis/crunchydata.com/v1/doc.go index 62cd5bc582..3108c58dfa 100644 --- a/pkg/apis/crunchydata.com/v1/doc.go +++ b/pkg/apis/crunchydata.com/v1/doc.go @@ -53,7 +53,7 @@ cluster. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.5.1", + '{"ClientVersion":"4.6.0-beta.1", "Namespace":"pgouser1", "Name":"mycluster", $PGO_APISERVER_URL/clusters @@ -72,7 +72,7 @@ show all of the clusters that are in the given namespace. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.5.1", + '{"ClientVersion":"4.6.0-beta.1", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/showclusters @@ -82,7 +82,7 @@ $PGO_APISERVER_URL/showclusters curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.5.1", + '{"ClientVersion":"4.6.0-beta.1", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/clustersdelete @@ -90,7 +90,7 @@ $PGO_APISERVER_URL/clustersdelete Schemes: http, https BasePath: / - Version: 4.5.1 + Version: 4.6.0-beta.1 License: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0 Contact: Crunchy Data https://www.crunchydata.com/ diff --git a/pkg/apiservermsgs/common.go b/pkg/apiservermsgs/common.go index d52499aa4a..db8319faaa 100644 --- a/pkg/apiservermsgs/common.go +++ b/pkg/apiservermsgs/common.go @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -const PGO_VERSION = "4.5.1" +const PGO_VERSION = "4.6.0-beta.1" // Ok status const Ok = "ok" diff --git a/redhat/atomic/help.1 b/redhat/atomic/help.1 index 6f9bfad143..aa67fa0e9a 100644 --- a/redhat/atomic/help.1 +++ b/redhat/atomic/help.1 @@ -56,4 +56,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa \fB\fCRelease=\fR .PP -The specific release number of the container. For example, Release="4.5.1" +The specific release number of the container. For example, Release="4.6.0-beta.1" diff --git a/redhat/atomic/help.md b/redhat/atomic/help.md index 1a12dbc144..4354a14dbf 100644 --- a/redhat/atomic/help.md +++ b/redhat/atomic/help.md @@ -45,4 +45,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa `Release=` -The specific release number of the container. For example, Release="4.5.1" +The specific release number of the container. For example, Release="4.6.0-beta.1" From 99c8f39d37b94c7e99fefa216a183569183c4d89 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 5 Jan 2021 08:49:12 -0500 Subject: [PATCH 119/276] Amend container compaction list The pg_basebackup restore container was compacted into crunchy-postgres --- docs/content/releases/4.6.0.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/content/releases/4.6.0.md b/docs/content/releases/4.6.0.md index d7719433cf..1b8f01fc41 100644 --- a/docs/content/releases/4.6.0.md +++ b/docs/content/releases/4.6.0.md @@ -142,6 +142,7 @@ Removed container images include: - `crunchy-admin` - `crunchy-backrest-restore` - `crunchy-backup` +- `crunchy-pgbasebackup-restore` - `crunchy-pgbench` - `crunchy-pgdump` - `crunchy-pgrestore` From 0d90315d3e5a02a7bff5948cce7f86c7a2a33503 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 5 Jan 2021 10:14:03 -0500 Subject: [PATCH 120/276] Remove relative relref from Hugo docs This appears to have been added in error and affects the official doc builds. --- docs/content/Upgrade/manual/upgrade35.md | 4 ++-- docs/content/Upgrade/manual/upgrade4.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/content/Upgrade/manual/upgrade35.md b/docs/content/Upgrade/manual/upgrade35.md index cb7ec25138..53704d6bf6 100644 --- a/docs/content/Upgrade/manual/upgrade35.md +++ b/docs/content/Upgrade/manual/upgrade35.md @@ -114,7 +114,7 @@ We strongly recommend that you create a test cluster before proceeding to the ne Once the Operator is installed and functional, create a new {{< param operatorVersion >}} cluster matching the cluster details recorded in Step 1. Be sure to use the primary PVC name (also noted in Step 1) and the same major PostgreSQL version as was used previously. This will allow the new clusters to utilize the existing PVCs. -NOTE: If you have existing pgBackRest backups stored that you would like to have available in the upgraded cluster, you will need to follow the [PVC Renaming Procedure]( {{< relref "Upgrade/manual/upgrade35#pgbackrest-repo-pvc-renaming" >}}). +NOTE: If you have existing pgBackRest backups stored that you would like to have available in the upgraded cluster, you will need to follow the [PVC Renaming Procedure](#pgbackrest-repo-pvc-renaming). A simple example is given below, but more information on cluster creation can be found [here](/pgo-client/common-tasks#creating-a-postgresql-cluster) @@ -226,7 +226,7 @@ spec: volumeName: "crunchy-pv156" ``` -where name matches your new cluster (Remember that this will need to match the "primary PVC" name identified in [Step 2]( {{< relref "Upgrade/manual/upgrade35#step-2" >}}) of the upgrade procedure!) and namespace, storageClassName, accessModes, storage, volumeMode and volumeName match your original PVC. +where name matches your new cluster (Remember that this will need to match the "primary PVC" name identified in [Step 2](#step-2) of the upgrade procedure!) and namespace, storageClassName, accessModes, storage, volumeMode and volumeName match your original PVC. ##### Step 6 diff --git a/docs/content/Upgrade/manual/upgrade4.md b/docs/content/Upgrade/manual/upgrade4.md index da11f86f15..f39439cfc6 100644 --- a/docs/content/Upgrade/manual/upgrade4.md +++ b/docs/content/Upgrade/manual/upgrade4.md @@ -151,7 +151,7 @@ We strongly recommend that you create a test cluster before proceeding to the ne Once the Operator is installed and functional, create a new {{< param operatorVersion >}} cluster matching the cluster details recorded in Step 1. Be sure to use the primary PVC name (also noted in Step 1) and the same major PostgreSQL version as was used previously. This will allow the new clusters to utilize the existing PVCs. -NOTE: If you have existing pgBackRest backups stored that you would like to have available in the upgraded cluster, you will need to follow the [PVC Renaming Procedure]( {{< relref "Upgrade/manual/upgrade4#pgbackrest-repo-pvc-renaming" >}}). +NOTE: If you have existing pgBackRest backups stored that you would like to have available in the upgraded cluster, you will need to follow the [PVC Renaming Procedure](#pgbackrest-repo-pvc-renaming). A simple example is given below, but more information on cluster creation can be found [here](/pgo-client/common-tasks#creating-a-postgresql-cluster) @@ -431,7 +431,7 @@ We strongly recommend that you create a test cluster before proceeding to the ne Once the Operator is installed and functional, create a new {{< param operatorVersion >}} cluster matching the cluster details recorded in Step 1. Be sure to use the same name and the same major PostgreSQL version as was used previously. This will allow the new clusters to utilize the existing PVCs. A simple example is given below, but more information on cluster creation can be found [here](/pgo-client/common-tasks#creating-a-postgresql-cluster) -NOTE: If you have existing pgBackRest backups stored that you would like to have available in the upgraded cluster, you will need to follow the [PVC Renaming Procedure]( {{< relref "Upgrade/manual/upgrade4#pgbackrest-repo-pvc-renaming" >}}). +NOTE: If you have existing pgBackRest backups stored that you would like to have available in the upgraded cluster, you will need to follow the [PVC Renaming Procedure](#pgbackrest-repo-pvc-renaming). ``` pgo create cluster -n @@ -543,7 +543,7 @@ spec: volumeName: "crunchy-pv156" ``` -where name matches your new cluster (Remember that this will need to match the "primary PVC" name identified in [Step 2]( {{< relref "Upgrade/manual/upgrade35#step-2" >}}) of the upgrade procedure!) and namespace, storageClassName, accessModes, storage, volumeMode and volumeName match your original PVC. +where name matches your new cluster (Remember that this will need to match the "primary PVC" name identified in [Step 2](#step-2) of the upgrade procedure!) and namespace, storageClassName, accessModes, storage, volumeMode and volumeName match your original PVC. ##### Step 6 From e4666c1c0d7dbd092d55e3c3260252c06731b819 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 5 Jan 2021 14:02:01 -0500 Subject: [PATCH 121/276] Adjustment to formatting in custom resource documentation This was breaking the PDF generation of the documentation for unexplained reasons. --- docs/content/custom-resources/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/custom-resources/_index.md b/docs/content/custom-resources/_index.md index 370a0aee25..62ddc459ba 100644 --- a/docs/content/custom-resources/_index.md +++ b/docs/content/custom-resources/_index.md @@ -747,7 +747,7 @@ make changes, as described below. | limits | `create`, `update` | Specify the container resource limits that the PostgreSQL cluster should use. Follows the [Kubernetes definitions of resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | | name | `create` | The name of the PostgreSQL instance that is the primary. On creation, this should be set to be the same as `ClusterName`. | | namespace | `create` | The Kubernetes Namespace that the PostgreSQL cluster is deployed in. | -| nodeAffinity | `create` | Sets the [node affinity rules]({{< relref "/architecture/high-availability/_index.md#node-affinity" >}}) for the PostgreSQL cluster and associated PostgreSQL instances. Can be overridden on a per-instance (`pgreplicas.crunchydata.com`) basis. Please see the `Node Affinity Specification` section below. | +| nodeAffinity | `create` | Sets the [node affinity rules](/architecture/high-availability/#node-affinity) for the PostgreSQL cluster and associated PostgreSQL instances. Can be overridden on a per-instance (`pgreplicas.crunchydata.com`) basis. Please see the `Node Affinity Specification` section below. | | pgBadger | `create`,`update` | If `true`, deploys the `crunchy-pgbadger` sidecar for query analysis. | | pgbadgerport | `create` | If the `PGBadger` label is set, then this specifies the port that the pgBadger sidecar runs on (e.g. `10000`) | | pgBouncer | `create`, `update` | If specified, defines the attributes to use for the pgBouncer connection pooling deployment that can be used in conjunction with this PostgreSQL cluster. Please see the specification defined below. | From d43dbac3c55e21db3a27fa804eeab6a6f1dee82b Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 5 Jan 2021 15:11:52 -0500 Subject: [PATCH 122/276] Allow for empty --service-type on `pgo create pgbouncer` This was an oversight in the logic for allowing a default pgBouncer Service Type to be set. Issue: [ch10065] --- internal/apiserver/pgbouncerservice/pgbouncerimpl.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/apiserver/pgbouncerservice/pgbouncerimpl.go b/internal/apiserver/pgbouncerservice/pgbouncerimpl.go index 5dfc3dfc47..92030852e5 100644 --- a/internal/apiserver/pgbouncerservice/pgbouncerimpl.go +++ b/internal/apiserver/pgbouncerservice/pgbouncerimpl.go @@ -116,7 +116,7 @@ func CreatePgbouncer(request *msgs.CreatePgbouncerRequest, ns, pgouser string) m resp.Status.Msg = fmt.Sprintf("invalid service type %q", request.ServiceType) return resp case v1.ServiceTypeClusterIP, v1.ServiceTypeNodePort, - v1.ServiceTypeLoadBalancer, v1.ServiceTypeExternalName: + v1.ServiceTypeLoadBalancer, v1.ServiceTypeExternalName, "": cluster.Spec.PgBouncer.ServiceType = request.ServiceType } From 01f59bfac2507a1ceeddc5a6ff33a868b344555a Mon Sep 17 00:00:00 2001 From: Joseph Mckulka <16840147+jmckulk@users.noreply.github.com> Date: Tue, 5 Jan 2021 16:05:50 -0500 Subject: [PATCH 123/276] Update failover e2e test As part of the move from failover to switchover the output message from the `pgo failover` command was updated. This change updates the test to account for the new output message. --- testing/pgo_cli/cluster_failover_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/pgo_cli/cluster_failover_test.go b/testing/pgo_cli/cluster_failover_test.go index ac4f2a40c6..e7b2f02585 100644 --- a/testing/pgo_cli/cluster_failover_test.go +++ b/testing/pgo_cli/cluster_failover_test.go @@ -68,7 +68,7 @@ func TestClusterFailover(t *testing.T) { "--target="+before[0].Labels["deployment-name"], "--no-prompt", ).Exec(t) require.NoError(t, err) - require.Contains(t, output, "created") + require.Contains(t, output, "success") replaced := func() bool { after := replicaPods(t, namespace(), cluster()) From 6069d0d6589097d21422b4e3f85a6622f91b4e9c Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 5 Jan 2021 16:16:29 -0500 Subject: [PATCH 124/276] Update build variable to default to PostgreSQL 13 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 62661ad617..4f5ca5aef1 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ PGO_BASEOS ?= centos8 PGO_IMAGE_PREFIX ?= crunchydata PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) PGO_VERSION ?= 4.6.0-beta.1 -PGO_PG_VERSION ?= 12 +PGO_PG_VERSION ?= 13 PGO_PG_FULLVERSION ?= 13.1 PGO_BACKREST_VERSION ?= 2.31 PACKAGER ?= yum From abab93812109f133272ec93a4f525eaf23ad814d Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Thu, 7 Jan 2021 11:05:41 -0500 Subject: [PATCH 125/276] Bump Prometheus to v2.24.0 --- docs/content/installation/metrics/metrics-configuration.md | 2 +- installers/metrics/ansible/roles/pgo-metrics/defaults/main.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/installation/metrics/metrics-configuration.md b/docs/content/installation/metrics/metrics-configuration.md index 559f9273ef..1c86eb7819 100644 --- a/docs/content/installation/metrics/metrics-configuration.md +++ b/docs/content/installation/metrics/metrics-configuration.md @@ -111,7 +111,7 @@ and tag as needed to use the RedHat certified containers: | `grafana_image_tag` | 6.7.5 | **Required** | Configures the image tag to use for the Grafana container. | | `prometheus_image_prefix` | prom | **Required** | Configures the image prefix to use for the Prometheus container. | | `prometheus_image_name` | promtheus | **Required** | Configures the image name to use for the Prometheus container. | -| `prometheus_image_tag` | v2.23.0 | **Required** | Configures the image tag to use for the Prometheus container. | +| `prometheus_image_tag` | v2.24.0 | **Required** | Configures the image tag to use for the Prometheus container. | Additionally, these same settings can be utilized as needed to support custom image names, tags, and additional container registries. diff --git a/installers/metrics/ansible/roles/pgo-metrics/defaults/main.yml b/installers/metrics/ansible/roles/pgo-metrics/defaults/main.yml index a16a017d63..cb6f5b6b4a 100644 --- a/installers/metrics/ansible/roles/pgo-metrics/defaults/main.yml +++ b/installers/metrics/ansible/roles/pgo-metrics/defaults/main.yml @@ -45,7 +45,7 @@ prometheus_custom_config: "" prometheus_install: "true" prometheus_image_prefix: "prom" prometheus_image_name: "prometheus" -prometheus_image_tag: "v2.23.0" +prometheus_image_tag: "v2.24.0" prometheus_port: "9090" prometheus_service_name: "crunchy-prometheus" prometheus_service_type: "ClusterIP" From f4e9de7e7a8c55e0606907c7a82faf953076b007 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Thu, 7 Jan 2021 11:51:11 -0500 Subject: [PATCH 126/276] Modify roles for pgo-target-role This moves the "replicasets" resource to be under the "apps" group, where it really should have been all along (at least since 1.9). This also adds an explicit permission for viewing pods/logs. Co-authored-by: Pramodh Mereddy Issue: [ch10081] --- deploy/cluster-roles.yaml | 11 ++++-- docs/content/architecture/namespace.md | 35 +++++++++++-------- .../files/pgo-configs/pgo-target-role.json | 18 ++++++++-- .../templates/cluster-rbac.yaml.j2 | 11 ++++-- installers/helm/templates/rbac.yaml | 3 +- installers/kubectl/postgres-operator.yml | 1 + 6 files changed, 57 insertions(+), 22 deletions(-) diff --git a/deploy/cluster-roles.yaml b/deploy/cluster-roles.yaml index cb0bb85b41..d760492836 100644 --- a/deploy/cluster-roles.yaml +++ b/deploy/cluster-roles.yaml @@ -41,8 +41,6 @@ rules: - endpoints - pods - pods/exec - - pods/log - - replicasets - secrets - services - persistentvolumeclaims @@ -55,10 +53,19 @@ rules: - update - delete - deletecollection + - apiGroups: + - '' + resources: + - pods/log + verbs: + - get + - list + - watch - apiGroups: - apps resources: - deployments + - replicasets verbs: - get - list diff --git a/docs/content/architecture/namespace.md b/docs/content/architecture/namespace.md index f6b4265723..1a551a8b91 100644 --- a/docs/content/architecture/namespace.md +++ b/docs/content/architecture/namespace.md @@ -34,8 +34,8 @@ settings. Enables full dynamic namespace capabilities, in which the Operator can create, delete and update any namespaces within a Kubernetes cluster. With `dynamic` mode enabled, the PostgreSQL Operator -can respond to namespace events in a Kubernetes cluster, such as when a namespace is created, and -take an appropriate action, such as adding the PostgreSQL Operator controllers for the newly +can respond to namespace events in a Kubernetes cluster, such as when a namespace is created, and +take an appropriate action, such as adding the PostgreSQL Operator controllers for the newly created namespace. The following defines the namespace permissions required for the `dynamic` mode to be enabled: @@ -62,8 +62,8 @@ rules: ### `readonly` -In `readonly` mode, the PostgreSQL Operator is still able to listen to namespace events within a -Kubernetes cluster, but it can no longer modify (create, update, delete) namespaces. For example, +In `readonly` mode, the PostgreSQL Operator is still able to listen to namespace events within a +Kubernetes cluster, but it can no longer modify (create, update, delete) namespaces. For example, if a Kubernetes administrator creates a namespace, the PostgreSQL Operator can respond and create controllers for that namespace. @@ -95,7 +95,7 @@ Operator is unable to dynamically respond to namespace events in the cluster, i target namespaces are deleted or new target namespaces need to be added, the PostgreSQL Operator will need to be re-deployed. -Please note that it is important to redeploy the PostgreSQL Operator following the deletion of a +Please note that it is important to redeploy the PostgreSQL Operator following the deletion of a target namespace to ensure it no longer attempts to listen for events in that namespace. The `disabled` mode is enabled the when the PostgreSQL Operator has not been assigned namespace @@ -103,22 +103,22 @@ permissions. ## RBAC Reconciliation -By default, the PostgreSQL Operator will attempt to reconcile RBAC resources (ServiceAccounts, +By default, the PostgreSQL Operator will attempt to reconcile RBAC resources (ServiceAccounts, Roles and RoleBindings) within each namespace configured for the PostgreSQL Operator installation. This allows the PostgreSQL Operator to create, update and delete the various RBAC resources it requires in order to properly create and manage PostgreSQL clusters within each targeted namespace (this includes self-healing RBAC resources as needed if removed and/or misconfigured). In order for RBAC reconciliation to function properly, the PostgreSQL Operator ServiceAccount must -be assigned a certain set of permissions. While the PostgreSQL Operator is not concerned with +be assigned a certain set of permissions. While the PostgreSQL Operator is not concerned with exactly how it has been assigned the permissions required to reconcile RBAC in each target -namespace, the various [installation methods]({{< relref "installation" >}}) supported by the +namespace, the various [installation methods]({{< relref "installation" >}}) supported by the PostgreSQL Operator install a recommended set permissions based on the specific Namespace Operating Mode enabled (see section [Namespace Operating Modes]({{< relref "#namespace-operating-modes" >}}) above for more information regarding the various Namespace Operating Modes available). -The following section defines the recommended set of permissions that should be assigned to the -PostgreSQL Operator ServiceAccount in order to ensure proper RBAC reconciliation based on the +The following section defines the recommended set of permissions that should be assigned to the +PostgreSQL Operator ServiceAccount in order to ensure proper RBAC reconciliation based on the specific Namespace Operating Mode enabled. Please note that each PostgreSQL Operator installation method handles the initial configuration and setup of the permissions shown below based on the Namespace Operating Mode configured during installation. @@ -127,7 +127,7 @@ Namespace Operating Mode configured during installation. When using the `dynamic` Namespace Operating Mode, it is recommended that the PostgreSQL Operator ServiceAccount be granted permissions to manage RBAC inside any namespace in the Kubernetes cluster -via a ClusterRole. This allows for a fully-hands off approach to managing RBAC within each +via a ClusterRole. This allows for a fully-hands off approach to managing RBAC within each targeted namespace space. In other words, as namespaces are added and removed post-installation of the PostgreSQL Operator (e.g. using `pgo create namespace` or `pgo delete namespace`), the Operator is able to automatically reconcile RBAC in those namespaces without the need for any external @@ -170,8 +170,6 @@ rules: - endpoints - pods - pods/exec - - pods/log - - replicasets - secrets - services - persistentvolumeclaims @@ -184,10 +182,19 @@ rules: - update - delete - deletecollection + - apiGroups: + - '' + resources: + - pods/log + verbs: + - get + - list + - watch - apiGroups: - apps resources: - deployments + - replicasets verbs: - get - list @@ -230,7 +237,7 @@ rules: ### `readonly` & `disabled` Namespace Operating Modes -When using the `readonly` or `disabled` Namespace Operating Modes, it is recommended that the +When using the `readonly` or `disabled` Namespace Operating Modes, it is recommended that the PostgreSQL Operator ServiceAccount be granted permissions to manage RBAC inside of any configured namespaces using local Roles within each targeted namespace. This means that as new namespaces are added and removed post-installation of the PostgreSQL Operator, an administrator must manually diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-target-role.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-target-role.json index 1cb6a31cc5..09b77ef469 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-target-role.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-target-role.json @@ -15,8 +15,6 @@ "endpoints", "pods", "pods/exec", - "pods/log", - "replicasets", "secrets", "services", "persistentvolumeclaims" @@ -32,12 +30,26 @@ "deletecollection" ] }, + { + "apiGroups": [ + "" + ], + "resources": [ + "pods/log" + ], + "verbs":[ + "get", + "list", + "watch" + ] + }, { "apiGroups": [ "apps" ], "resources": [ - "deployments" + "deployments", + "replicasets" ], "verbs":[ "get", diff --git a/installers/ansible/roles/pgo-operator/templates/cluster-rbac.yaml.j2 b/installers/ansible/roles/pgo-operator/templates/cluster-rbac.yaml.j2 index 771080042e..4212d9107b 100644 --- a/installers/ansible/roles/pgo-operator/templates/cluster-rbac.yaml.j2 +++ b/installers/ansible/roles/pgo-operator/templates/cluster-rbac.yaml.j2 @@ -42,8 +42,6 @@ rules: - endpoints - pods - pods/exec - - pods/log - - replicasets - secrets - services - persistentvolumeclaims @@ -56,10 +54,19 @@ rules: - update - delete - deletecollection + - apiGroups: + - '' + resources: + - pods/log + verbs: + - get + - list + - watch - apiGroups: - apps resources: - deployments + - replicasets verbs: - get - list diff --git a/installers/helm/templates/rbac.yaml b/installers/helm/templates/rbac.yaml index dbef140471..19d6fc06e4 100644 --- a/installers/helm/templates/rbac.yaml +++ b/installers/helm/templates/rbac.yaml @@ -73,6 +73,7 @@ rules: - extensions resources: - deployments + - replicasets verbs: - get - list @@ -145,4 +146,4 @@ subjects: - kind: ServiceAccount name: {{ include "postgres-operator.serviceAccountName" . }} namespace: {{ .Release.Namespace }} -{{ end }} \ No newline at end of file +{{ end }} diff --git a/installers/kubectl/postgres-operator.yml b/installers/kubectl/postgres-operator.yml index f5a7c18178..2a595b3b09 100644 --- a/installers/kubectl/postgres-operator.yml +++ b/installers/kubectl/postgres-operator.yml @@ -60,6 +60,7 @@ rules: - extensions resources: - deployments + - replicasets verbs: - get - list From 7aed5450df5688293b366183da01913d2f787f77 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Thu, 7 Jan 2021 11:55:20 -0500 Subject: [PATCH 127/276] Bump v4.6.0-beta.2 --- Makefile | 2 +- bin/push-ccp-to-gcr.sh | 2 +- conf/postgres-operator/pgo.yaml | 4 ++-- docs/config.toml | 2 +- docs/content/releases/4.6.0.md | 3 ++- examples/create-by-resource/fromcrd.json | 6 +++--- examples/envs.sh | 2 +- .../helm/create-cluster/templates/pgcluster.yaml | 2 +- examples/helm/create-cluster/values.yaml | 4 ++-- examples/kustomize/createcluster/README.md | 16 ++++++++-------- .../kustomize/createcluster/base/pgcluster.yaml | 6 +++--- .../overlay/staging/hippo-rpl1-pgreplica.yaml | 2 +- installers/ansible/README.md | 2 +- installers/ansible/values.yaml | 6 +++--- installers/gcp-marketplace/Makefile | 2 +- installers/gcp-marketplace/README.md | 2 +- installers/gcp-marketplace/values.yaml | 6 +++--- installers/helm/Chart.yaml | 2 +- installers/helm/values.yaml | 6 +++--- installers/kubectl/client-setup.sh | 2 +- installers/kubectl/postgres-operator-ocp311.yml | 8 ++++---- installers/kubectl/postgres-operator.yml | 8 ++++---- installers/metrics/ansible/README.md | 2 +- installers/metrics/helm/Chart.yaml | 2 +- installers/metrics/helm/helm_template.yaml | 2 +- installers/metrics/helm/values.yaml | 2 +- .../kubectl/postgres-operator-metrics-ocp311.yml | 2 +- .../kubectl/postgres-operator-metrics.yml | 2 +- installers/olm/Makefile | 2 +- pkg/apis/crunchydata.com/v1/doc.go | 8 ++++---- pkg/apiservermsgs/common.go | 2 +- redhat/atomic/help.1 | 2 +- redhat/atomic/help.md | 2 +- 33 files changed, 62 insertions(+), 61 deletions(-) diff --git a/Makefile b/Makefile index 4f5ca5aef1..63af50dea4 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ PGOROOT ?= $(CURDIR) PGO_BASEOS ?= centos8 PGO_IMAGE_PREFIX ?= crunchydata PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) -PGO_VERSION ?= 4.6.0-beta.1 +PGO_VERSION ?= 4.6.0-beta.2 PGO_PG_VERSION ?= 13 PGO_PG_FULLVERSION ?= 13.1 PGO_BACKREST_VERSION ?= 2.31 diff --git a/bin/push-ccp-to-gcr.sh b/bin/push-ccp-to-gcr.sh index a235fecf59..1f3838f374 100755 --- a/bin/push-ccp-to-gcr.sh +++ b/bin/push-ccp-to-gcr.sh @@ -16,7 +16,7 @@ GCR_IMAGE_PREFIX=gcr.io/crunchy-dev-test CCP_IMAGE_PREFIX=crunchydata -CCP_IMAGE_TAG=centos8-13.1-4.6.0-beta.1 +CCP_IMAGE_TAG=centos8-13.1-4.6.0-beta.2 IMAGES=( crunchy-prometheus diff --git a/conf/postgres-operator/pgo.yaml b/conf/postgres-operator/pgo.yaml index 2b0de113bf..28493c8c4a 100644 --- a/conf/postgres-operator/pgo.yaml +++ b/conf/postgres-operator/pgo.yaml @@ -2,7 +2,7 @@ Cluster: CCPImagePrefix: registry.developers.crunchydata.com/crunchydata Metrics: false Badger: false - CCPImageTag: centos8-13.1-4.6.0-beta.1 + CCPImageTag: centos8-13.1-4.6.0-beta.2 Port: 5432 PGBadgerPort: 10000 ExporterPort: 9187 @@ -81,4 +81,4 @@ Storage: Pgo: Audit: false PGOImagePrefix: registry.developers.crunchydata.com/crunchydata - PGOImageTag: centos8-4.6.0-beta.1 + PGOImageTag: centos8-4.6.0-beta.2 diff --git a/docs/config.toml b/docs/config.toml index 3a59d4523e..7975290ef6 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -25,7 +25,7 @@ disableNavChevron = false # set true to hide next/prev chevron, default is false highlightClientSide = false # set true to use highlight.pack.js instead of the default hugo chroma highlighter menushortcutsnewtab = true # set true to open shortcuts links to a new tab/window enableGitInfo = true -operatorVersion = "4.6.0-beta.1" +operatorVersion = "4.6.0-beta.2" postgresVersion = "13.1" postgresVersion13 = "13.1" postgresVersion12 = "13.1" diff --git a/docs/content/releases/4.6.0.md b/docs/content/releases/4.6.0.md index 1b8f01fc41..3f423fe16d 100644 --- a/docs/content/releases/4.6.0.md +++ b/docs/content/releases/4.6.0.md @@ -22,7 +22,7 @@ The PostgreSQL Operator 4.6.0 release includes the following software versions u The monitoring stack for the PostgreSQL Operator uses upstream components as opposed to repackaging them. These are specified as part of the [PostgreSQL Operator Installer](https://access.crunchydata.com/documentation/postgres-operator/latest/installation/postgres-operator/). We have tested this release with the following versions of each component: -- Prometheus: 2.23.0 +- Prometheus: 2.24.0 - Grafana: 6.7.5 - Alertmanager: 0.21.0 @@ -230,5 +230,6 @@ Passing in the [`--process-max`](https://pgbackrest.org/command.html#command-arc - Remove legacy `defaultMode` setting on the volume instructions for the pgBackRest repo Secret as the `readOnly` setting is used on the mount itself. Reported by (@szhang1). - The logger no longer defaults to using a log level of `DEBUG`. - Autofailover is no longer disabled when an `rmdata` Job is run, enabling a clean database shutdown process when deleting a PostgreSQL cluster. +- Update `pgo-target` permissions to match expectations for modern Kubernetes versions. - Major upgrade container now includes references for `pgnodemx`. - During a major upgrade, ensure permissions are correct on the old data directory before running `pg_upgrade`. diff --git a/examples/create-by-resource/fromcrd.json b/examples/create-by-resource/fromcrd.json index dc399d2fd0..f1f2043f14 100644 --- a/examples/create-by-resource/fromcrd.json +++ b/examples/create-by-resource/fromcrd.json @@ -10,7 +10,7 @@ "deployment-name": "fromcrd", "name": "fromcrd", "pg-cluster": "fromcrd", - "pgo-version": "4.6.0-beta.1", + "pgo-version": "4.6.0-beta.2", "pgouser": "pgoadmin" }, "name": "fromcrd", @@ -45,7 +45,7 @@ "supplementalgroups": "" }, "ccpimage": "crunchy-postgres-ha", - "ccpimagetag": "centos8-13.1-4.6.0-beta.1", + "ccpimagetag": "centos8-13.1-4.6.0-beta.2", "clustername": "fromcrd", "database": "userdb", "exporterport": "9187", @@ -60,7 +60,7 @@ "port": "5432", "user": "testuser", "userlabels": { - "pgo-version": "4.6.0-beta.1" + "pgo-version": "4.6.0-beta.2" } } } diff --git a/examples/envs.sh b/examples/envs.sh index 1811e2d5f7..37fa831db8 100644 --- a/examples/envs.sh +++ b/examples/envs.sh @@ -20,7 +20,7 @@ export PGO_CONF_DIR=$PGOROOT/installers/ansible/roles/pgo-operator/files # the version of the Operator you run is set by these vars export PGO_IMAGE_PREFIX=registry.developers.crunchydata.com/crunchydata export PGO_BASEOS=centos8 -export PGO_VERSION=4.6.0-beta.1 +export PGO_VERSION=4.6.0-beta.2 export PGO_IMAGE_TAG=$PGO_BASEOS-$PGO_VERSION # for setting the pgo apiserver port, disabling TLS or not verifying TLS diff --git a/examples/helm/create-cluster/templates/pgcluster.yaml b/examples/helm/create-cluster/templates/pgcluster.yaml index d137aeef5b..eb609def15 100644 --- a/examples/helm/create-cluster/templates/pgcluster.yaml +++ b/examples/helm/create-cluster/templates/pgcluster.yaml @@ -10,7 +10,7 @@ metadata: deployment-name: {{ .Values.pgclustername }} name: {{ .Values.pgclustername }} pg-cluster: {{ .Values.pgclustername }} - pgo-version: 4.6.0-beta.1 + pgo-version: 4.6.0-beta.2 pgouser: admin name: {{ .Values.pgclustername }} namespace: {{ .Values.namespace }} diff --git a/examples/helm/create-cluster/values.yaml b/examples/helm/create-cluster/values.yaml index af38177a81..bfc9b73bb0 100644 --- a/examples/helm/create-cluster/values.yaml +++ b/examples/helm/create-cluster/values.yaml @@ -4,11 +4,11 @@ # The values is for the namespace and the postgresql cluster name ccpimage: crunchy-postgres-ha ccpimageprefix: registry.developers.crunchydata.com/crunchydata -ccpimagetag: centos8-13.1-4.6.0-beta.1 +ccpimagetag: centos8-13.1-4.6.0-beta.2 namespace: pgo pgclustername: hippo pgoimageprefix: registry.developers.crunchydata.com/crunchydata -pgoversion: 4.6.0-beta.1 +pgoversion: 4.6.0-beta.2 hipposecretuser: "hippo" hipposecretpassword: "Supersecurepassword*" postgressecretuser: "postgres" diff --git a/examples/kustomize/createcluster/README.md b/examples/kustomize/createcluster/README.md index 1b03c207e5..8cdd77ab04 100644 --- a/examples/kustomize/createcluster/README.md +++ b/examples/kustomize/createcluster/README.md @@ -44,13 +44,13 @@ pgo show cluster hippo -n pgo ``` You will see something like this if successful: ``` -cluster : hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.1) +cluster : hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.2) pod : hippo-8fb6bd96-j87wq (Running) on gke-xxxx-default-pool-38e946bd-257w (1/1) (primary) pvc: hippo (1Gi) deployment : hippo deployment : hippo-backrest-shared-repo service : hippo - ClusterIP (10.0.56.86) - Ports (2022/TCP, 5432/TCP) - labels : pgo-version=4.6.0-beta.1 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.0-beta.2 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata ``` Feel free to run other pgo cli commands on the hippo cluster @@ -79,7 +79,7 @@ pgo show cluster dev-hippo -n pgo ``` You will see something like this if successful: ``` -cluster : dev-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.1) +cluster : dev-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.2) pod : dev-hippo-588d4cb746-bwrxb (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: dev-hippo (1Gi) deployment : dev-hippo @@ -87,7 +87,7 @@ cluster : dev-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.1) deployment : dev-hippo-pgbouncer service : dev-hippo - ClusterIP (10.0.62.87) - Ports (2022/TCP, 5432/TCP) service : dev-hippo-pgbouncer - ClusterIP (10.0.48.120) - Ports (5432/TCP) - labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.6.0-beta.1 pgouser=admin + labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.6.0-beta.2 pgouser=admin ``` #### staging The staging overlay will deploy a crunchy postgreSQL cluster with 2 replica's with annotations added @@ -113,7 +113,7 @@ pgo show cluster staging-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : staging-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.1) +cluster : staging-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.2) pod : staging-hippo-85cf6dcb65-9h748 (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: staging-hippo (1Gi) pod : staging-hippo-lnxw-cf47d8c8b-6r4wn (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (replica) @@ -128,7 +128,7 @@ cluster : staging-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.1) service : staging-hippo-replica - ClusterIP (10.0.56.57) - Ports (2022/TCP, 5432/TCP) pgreplica : staging-hippo-lnxw pgreplica : staging-hippo-rpl1 - labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.6.0-beta.1 pgouser=admin vendor=crunchydata + labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.6.0-beta.2 pgouser=admin vendor=crunchydata ``` #### production @@ -154,7 +154,7 @@ pgo show cluster prod-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : prod-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.1) +cluster : prod-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.2) pod : prod-hippo-5d6dd46497-rr67c (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (primary) pvc: prod-hippo (1Gi) pod : prod-hippo-flty-84d97c8769-2pzbh (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (replica) @@ -165,7 +165,7 @@ cluster : prod-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.1) service : prod-hippo - ClusterIP (10.0.56.18) - Ports (2022/TCP, 5432/TCP) service : prod-hippo-replica - ClusterIP (10.0.56.101) - Ports (2022/TCP, 5432/TCP) pgreplica : prod-hippo-flty - labels : pgo-version=4.6.0-beta.1 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.0-beta.2 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata ``` ### Delete the clusters To delete the clusters run the following pgo cli commands diff --git a/examples/kustomize/createcluster/base/pgcluster.yaml b/examples/kustomize/createcluster/base/pgcluster.yaml index 91c0ad713c..f89650796b 100644 --- a/examples/kustomize/createcluster/base/pgcluster.yaml +++ b/examples/kustomize/createcluster/base/pgcluster.yaml @@ -10,7 +10,7 @@ metadata: deployment-name: hippo name: hippo pg-cluster: hippo - pgo-version: 4.6.0-beta.1 + pgo-version: 4.6.0-beta.2 pgouser: admin name: hippo namespace: pgo @@ -42,7 +42,7 @@ spec: annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: registry.developers.crunchydata.com/crunchydata - ccpimagetag: centos8-13.1-4.6.0-beta.1 + ccpimagetag: centos8-13.1-4.6.0-beta.2 clustername: hippo customconfig: "" database: hippo @@ -63,4 +63,4 @@ spec: port: "5432" user: hippo userlabels: - pgo-version: 4.6.0-beta.1 + pgo-version: 4.6.0-beta.2 diff --git a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml index c371817730..359350bb6f 100644 --- a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml +++ b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml @@ -20,4 +20,4 @@ spec: storagetype: dynamic supplementalgroups: "" userlabels: - pgo-version: 4.6.0-beta.1 + pgo-version: 4.6.0-beta.2 diff --git a/installers/ansible/README.md b/installers/ansible/README.md index b82f698d2a..4037651766 100644 --- a/installers/ansible/README.md +++ b/installers/ansible/README.md @@ -4,7 +4,7 @@ Crunchy Data

-Latest Release: 4.6.0-beta.1 +Latest Release: 4.6.0-beta.2 ## General diff --git a/installers/ansible/values.yaml b/installers/ansible/values.yaml index c8ee2b641a..bc6a9a7048 100644 --- a/installers/ansible/values.yaml +++ b/installers/ansible/values.yaml @@ -17,7 +17,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.1-4.6.0-beta.1" +ccp_image_tag: "centos8-13.1-4.6.0-beta.2" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -50,14 +50,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.0-beta.1" +pgo_client_version: "4.6.0-beta.2" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos8-4.6.0-beta.1" +pgo_image_tag: "centos8-4.6.0-beta.2" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/gcp-marketplace/Makefile b/installers/gcp-marketplace/Makefile index ac2e205f8b..c344f572e8 100644 --- a/installers/gcp-marketplace/Makefile +++ b/installers/gcp-marketplace/Makefile @@ -6,7 +6,7 @@ MARKETPLACE_TOOLS ?= gcr.io/cloud-marketplace-tools/k8s/dev:$(MARKETPLACE_VERSIO MARKETPLACE_VERSION ?= 0.9.4 KUBECONFIG ?= $(HOME)/.kube/config PARAMETERS ?= {} -PGO_VERSION ?= 4.6.0-beta.1 +PGO_VERSION ?= 4.6.0-beta.2 IMAGE_BUILD_ARGS = --build-arg MARKETPLACE_VERSION='$(MARKETPLACE_VERSION)' \ --build-arg PGO_VERSION='$(PGO_VERSION)' diff --git a/installers/gcp-marketplace/README.md b/installers/gcp-marketplace/README.md index cd71a9b719..4d204df786 100644 --- a/installers/gcp-marketplace/README.md +++ b/installers/gcp-marketplace/README.md @@ -59,7 +59,7 @@ Google Cloud Marketplace. ```shell IMAGE_REPOSITORY=gcr.io/crunchydata-public/postgres-operator - export PGO_VERSION=4.6.0-beta.1 + export PGO_VERSION=4.6.0-beta.2 export INSTALLER_IMAGE=${IMAGE_REPOSITORY}/deployer:${PGO_VERSION} export OPERATOR_IMAGE=${IMAGE_REPOSITORY}:${PGO_VERSION} export OPERATOR_IMAGE_API=${IMAGE_REPOSITORY}/pgo-apiserver:${PGO_VERSION} diff --git a/installers/gcp-marketplace/values.yaml b/installers/gcp-marketplace/values.yaml index 3be2c2e119..7bd7a6c903 100644 --- a/installers/gcp-marketplace/values.yaml +++ b/installers/gcp-marketplace/values.yaml @@ -10,7 +10,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.1-4.6.0-beta.1" +ccp_image_tag: "centos8-13.1-4.6.0-beta.2" create_rbac: "true" db_name: "" db_password_age_days: "0" @@ -32,9 +32,9 @@ pgo_admin_role_name: "pgoadmin" pgo_admin_username: "admin" pgo_client_container_install: "false" pgo_client_install: 'false' -pgo_client_version: "4.6.0-beta.1" +pgo_client_version: "4.6.0-beta.2" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.0-beta.1" +pgo_image_tag: "centos8-4.6.0-beta.2" pgo_installation_name: '${OPERATOR_NAME}' pgo_operator_namespace: '${OPERATOR_NAMESPACE}' scheduler_timeout: "3600" diff --git a/installers/helm/Chart.yaml b/installers/helm/Chart.yaml index a597693462..3f03cf2db0 100644 --- a/installers/helm/Chart.yaml +++ b/installers/helm/Chart.yaml @@ -3,7 +3,7 @@ name: postgres-operator description: Crunchy PostgreSQL Operator Helm chart for Kubernetes type: application version: 0.1.0 -appVersion: 4.6.0-beta.1 +appVersion: 4.6.0-beta.2 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/crunchy_logo.png keywords: diff --git a/installers/helm/values.yaml b/installers/helm/values.yaml index 272b6f5ebc..bb5ce533cf 100644 --- a/installers/helm/values.yaml +++ b/installers/helm/values.yaml @@ -37,7 +37,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.1-4.6.0-beta.1" +ccp_image_tag: "centos8-13.1-4.6.0-beta.2" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -70,14 +70,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.0-beta.1" +pgo_client_version: "4.6.0-beta.2" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos8-4.6.0-beta.1" +pgo_image_tag: "centos8-4.6.0-beta.2" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/kubectl/client-setup.sh b/installers/kubectl/client-setup.sh index a98b99de5b..e225efa34a 100755 --- a/installers/kubectl/client-setup.sh +++ b/installers/kubectl/client-setup.sh @@ -14,7 +14,7 @@ # This script should be run after the operator has been deployed PGO_OPERATOR_NAMESPACE="${PGO_OPERATOR_NAMESPACE:-pgo}" PGO_USER_ADMIN="${PGO_USER_ADMIN:-pgouser-admin}" -PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.0-beta.1}" +PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.0-beta.2}" PGO_CLIENT_URL="https://github.com/CrunchyData/postgres-operator/releases/download/${PGO_CLIENT_VERSION}" PGO_CMD="${PGO_CMD-kubectl}" diff --git a/installers/kubectl/postgres-operator-ocp311.yml b/installers/kubectl/postgres-operator-ocp311.yml index cb1b16c96d..0baa77037d 100644 --- a/installers/kubectl/postgres-operator-ocp311.yml +++ b/installers/kubectl/postgres-operator-ocp311.yml @@ -44,7 +44,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos8-13.1-4.6.0-beta.1" + ccp_image_tag: "centos8-13.1-4.6.0-beta.2" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -77,14 +77,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.0-beta.1" + pgo_client_version: "4.6.0-beta.2" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos8-4.6.0-beta.1" + pgo_image_tag: "centos8-4.6.0-beta.2" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -161,7 +161,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-beta.1 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-beta.2 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/kubectl/postgres-operator.yml b/installers/kubectl/postgres-operator.yml index 2a595b3b09..3b0764f057 100644 --- a/installers/kubectl/postgres-operator.yml +++ b/installers/kubectl/postgres-operator.yml @@ -139,7 +139,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos8-13.1-4.6.0-beta.1" + ccp_image_tag: "centos8-13.1-4.6.0-beta.2" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -172,14 +172,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.0-beta.1" + pgo_client_version: "4.6.0-beta.2" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos8-4.6.0-beta.1" + pgo_image_tag: "centos8-4.6.0-beta.2" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -269,7 +269,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-beta.1 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-beta.2 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/ansible/README.md b/installers/metrics/ansible/README.md index 1be5088c15..41f7d7690d 100644 --- a/installers/metrics/ansible/README.md +++ b/installers/metrics/ansible/README.md @@ -4,7 +4,7 @@ Crunchy Data

-Latest Release: 4.6.0-beta.1 +Latest Release: 4.6.0-beta.2 ## General diff --git a/installers/metrics/helm/Chart.yaml b/installers/metrics/helm/Chart.yaml index a368325252..5a8debf064 100644 --- a/installers/metrics/helm/Chart.yaml +++ b/installers/metrics/helm/Chart.yaml @@ -3,6 +3,6 @@ name: postgres-operator-monitoring description: Install for Crunchy PostgreSQL Operator Monitoring type: application version: 0.1.0 -appVersion: 4.6.0-beta.1 +appVersion: 4.6.0-beta.2 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/crunchy_logo.png \ No newline at end of file diff --git a/installers/metrics/helm/helm_template.yaml b/installers/metrics/helm/helm_template.yaml index 9cef2423b4..3f89c0d2b5 100644 --- a/installers/metrics/helm/helm_template.yaml +++ b/installers/metrics/helm/helm_template.yaml @@ -20,5 +20,5 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.0-beta.1" +pgo_image_tag: "centos8-4.6.0-beta.2" diff --git a/installers/metrics/helm/values.yaml b/installers/metrics/helm/values.yaml index 3baa0c0c04..b7f79ed8de 100644 --- a/installers/metrics/helm/values.yaml +++ b/installers/metrics/helm/values.yaml @@ -20,7 +20,7 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.0-beta.1" +pgo_image_tag: "centos8-4.6.0-beta.2" # ===================== # Configuration Options diff --git a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml index 984fb0f6d1..b449881000 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml @@ -96,7 +96,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-beta.1 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-beta.2 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/kubectl/postgres-operator-metrics.yml b/installers/metrics/kubectl/postgres-operator-metrics.yml index d005d00200..dea5d62e58 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics.yml @@ -165,7 +165,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-beta.1 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-beta.2 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/olm/Makefile b/installers/olm/Makefile index a5f7116d43..d33f040de4 100644 --- a/installers/olm/Makefile +++ b/installers/olm/Makefile @@ -11,7 +11,7 @@ OLM_TOOLS ?= registry.localhost:5000/postgres-operator-olm-tools:$(OLM_SDK_VERSI OLM_VERSION ?= 0.15.1 PGO_BASEOS ?= centos8 PGO_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -PGO_VERSION ?= 4.6.0-beta.1 +PGO_VERSION ?= 4.6.0-beta.2 PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) CCP_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(PGO_VERSION) CCP_POSTGIS_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(CCP_POSTGIS_VERSION)-$(PGO_VERSION) diff --git a/pkg/apis/crunchydata.com/v1/doc.go b/pkg/apis/crunchydata.com/v1/doc.go index 3108c58dfa..4c92f8a983 100644 --- a/pkg/apis/crunchydata.com/v1/doc.go +++ b/pkg/apis/crunchydata.com/v1/doc.go @@ -53,7 +53,7 @@ cluster. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.0-beta.1", + '{"ClientVersion":"4.6.0-beta.2", "Namespace":"pgouser1", "Name":"mycluster", $PGO_APISERVER_URL/clusters @@ -72,7 +72,7 @@ show all of the clusters that are in the given namespace. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.0-beta.1", + '{"ClientVersion":"4.6.0-beta.2", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/showclusters @@ -82,7 +82,7 @@ $PGO_APISERVER_URL/showclusters curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.0-beta.1", + '{"ClientVersion":"4.6.0-beta.2", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/clustersdelete @@ -90,7 +90,7 @@ $PGO_APISERVER_URL/clustersdelete Schemes: http, https BasePath: / - Version: 4.6.0-beta.1 + Version: 4.6.0-beta.2 License: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0 Contact: Crunchy Data https://www.crunchydata.com/ diff --git a/pkg/apiservermsgs/common.go b/pkg/apiservermsgs/common.go index db8319faaa..b2fabc8e0d 100644 --- a/pkg/apiservermsgs/common.go +++ b/pkg/apiservermsgs/common.go @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -const PGO_VERSION = "4.6.0-beta.1" +const PGO_VERSION = "4.6.0-beta.2" // Ok status const Ok = "ok" diff --git a/redhat/atomic/help.1 b/redhat/atomic/help.1 index aa67fa0e9a..afb2703167 100644 --- a/redhat/atomic/help.1 +++ b/redhat/atomic/help.1 @@ -56,4 +56,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa \fB\fCRelease=\fR .PP -The specific release number of the container. For example, Release="4.6.0-beta.1" +The specific release number of the container. For example, Release="4.6.0-beta.2" diff --git a/redhat/atomic/help.md b/redhat/atomic/help.md index 4354a14dbf..a18c5a8293 100644 --- a/redhat/atomic/help.md +++ b/redhat/atomic/help.md @@ -45,4 +45,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa `Release=` -The specific release number of the container. For example, Release="4.6.0-beta.1" +The specific release number of the container. For example, Release="4.6.0-beta.2" From d2d8e8111d714a4fd373553cbb5d06225f381635 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Fri, 8 Jan 2021 14:53:05 -0500 Subject: [PATCH 128/276] Ensure ECDSA keygen explicitly specifies a named curve In the EL7 base images, OpenSSL does not appear to explicitly state that the generated ECDSA key is generated using a named curve, whereas Go explicitly expects this to be the case: https://github.com/golang/go/issues/21502#issuecomment-323400475 The issue manifests itself as part of the "pgo-deployer" when the API server key/certificate pair is created using `kubectl create secrets tls`, which does some level of introspection. The fix is to explicitly specify using a named curve, which works both in the EL7 and EL8 images. Issue: [ch10097] --- deploy/gen-api-keys.sh | 1 + installers/ansible/roles/pgo-operator/tasks/certs.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/deploy/gen-api-keys.sh b/deploy/gen-api-keys.sh index 5fd1139ced..3d59b8aa99 100755 --- a/deploy/gen-api-keys.sh +++ b/deploy/gen-api-keys.sh @@ -19,6 +19,7 @@ openssl req \ -nodes \ -newkey ec \ -pkeyopt ec_paramgen_curve:prime256v1 \ + -pkeyopt ec_param_enc:named_curve \ -sha384 \ -keyout $PGOROOT/conf/postgres-operator/server.key \ -out $PGOROOT/conf/postgres-operator/server.crt \ diff --git a/installers/ansible/roles/pgo-operator/tasks/certs.yml b/installers/ansible/roles/pgo-operator/tasks/certs.yml index 07e3077eee..83f2697df0 100644 --- a/installers/ansible/roles/pgo-operator/tasks/certs.yml +++ b/installers/ansible/roles/pgo-operator/tasks/certs.yml @@ -12,6 +12,7 @@ -nodes \ -newkey ec \ -pkeyopt ec_paramgen_curve:prime256v1 \ + -pkeyopt ec_param_enc:named_curve \ -sha384 \ -days 1825 \ -subj "/CN=*" \ From a7359f58f62f4ad8f216bc9f0054670e66d18d09 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Fri, 8 Jan 2021 17:00:25 -0500 Subject: [PATCH 129/276] Another pgMonitor version bump I missed this one when bumping the verison in bd046f68 --- installers/metrics/ansible/roles/pgo-metrics/defaults/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/installers/metrics/ansible/roles/pgo-metrics/defaults/main.yml b/installers/metrics/ansible/roles/pgo-metrics/defaults/main.yml index cb6f5b6b4a..b2e217360b 100644 --- a/installers/metrics/ansible/roles/pgo-metrics/defaults/main.yml +++ b/installers/metrics/ansible/roles/pgo-metrics/defaults/main.yml @@ -9,7 +9,7 @@ delete_metrics_namespace: "false" metrics_namespace: "pgo" metrics_image_pull_secret: "" metrics_image_pull_secret_manifest: "" -pgmonitor_version: "v4.4-RC7" +pgmonitor_version: "v4.4" alertmanager_configmap: "alertmanager-config" alertmanager_rules_configmap: "alertmanager-rules-config" From 78f22da4d3db614c57323f41a68edd5684a4ce13 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 10 Jan 2021 09:38:40 -0500 Subject: [PATCH 130/276] Move common job into general utility area This moves the "rmdata" Job creator into its own area. This is to prepare for future work where the Job creator can be called from a different package. --- cmd/pgo-rmdata/README.txt | 6 --- .../apiserver/clusterservice/clusterimpl.go | 3 +- .../apiserver/clusterservice/scaleimpl.go | 4 +- internal/apiserver/common.go | 40 ------------------- internal/util/cluster.go | 39 ++++++++++++++++++ 5 files changed, 42 insertions(+), 50 deletions(-) delete mode 100644 cmd/pgo-rmdata/README.txt diff --git a/cmd/pgo-rmdata/README.txt b/cmd/pgo-rmdata/README.txt deleted file mode 100644 index 3361973ff1..0000000000 --- a/cmd/pgo-rmdata/README.txt +++ /dev/null @@ -1,6 +0,0 @@ - -you can test this program outside of a container like so: - -cd $PGOROOT - -go run ./pgo-rmdata/pgo-rmdata.go -pg-cluster=mycluster -replica-name= -namespace=mynamespace -remove-data=true -remove-backup=true -is-replica=false -is-backup=false diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index 12b8604391..c68ab5d406 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -109,8 +109,7 @@ func DeleteCluster(name, selector string, deleteData, deleteBackups bool, ns, pg return response } - err := apiserver.CreateRMDataTask(cluster.Spec.Name, replicaName, taskName, deleteBackups, deleteData, isReplica, isBackup, ns, clusterPGHAScope) - if err != nil { + if err := util.CreateRMDataTask(apiserver.Clientset, cluster.Spec.Name, replicaName, taskName, deleteBackups, deleteData, isReplica, isBackup, ns, clusterPGHAScope); err != nil { log.Debugf("error on creating rmdata task %s", err.Error()) response.Status.Code = msgs.Error response.Status.Msg = err.Error() diff --git a/internal/apiserver/clusterservice/scaleimpl.go b/internal/apiserver/clusterservice/scaleimpl.go index f8ee7c57d8..c3426b2f34 100644 --- a/internal/apiserver/clusterservice/scaleimpl.go +++ b/internal/apiserver/clusterservice/scaleimpl.go @@ -294,8 +294,8 @@ func ScaleDown(deleteData bool, clusterName, replicaName, ns string) msgs.ScaleD isReplica := true isBackup := false taskName := replicaName + "-rmdata" - err = apiserver.CreateRMDataTask(clusterName, replicaName, taskName, deleteBackups, deleteData, isReplica, isBackup, ns, clusterPGHAScope) - if err != nil { + + if err := util.CreateRMDataTask(apiserver.Clientset, clusterName, replicaName, taskName, deleteBackups, deleteData, isReplica, isBackup, ns, clusterPGHAScope); err != nil { response.Status.Code = msgs.Error response.Status.Msg = err.Error() return response diff --git a/internal/apiserver/common.go b/internal/apiserver/common.go index 08bc42f6ac..50e0db963a 100644 --- a/internal/apiserver/common.go +++ b/internal/apiserver/common.go @@ -19,10 +19,8 @@ import ( "context" "errors" "fmt" - "strconv" "strings" - "github.com/crunchydata/postgres-operator/internal/config" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" log "github.com/sirupsen/logrus" @@ -56,44 +54,6 @@ var ( "Operator installation") ) -func CreateRMDataTask(clusterName, replicaName, taskName string, deleteBackups, deleteData, isReplica, isBackup bool, ns, clusterPGHAScope string) error { - ctx := context.TODO() - var err error - - // create pgtask CRD - spec := crv1.PgtaskSpec{} - spec.Namespace = ns - spec.Name = taskName - spec.TaskType = crv1.PgtaskDeleteData - - spec.Parameters = make(map[string]string) - spec.Parameters[config.LABEL_DELETE_DATA] = strconv.FormatBool(deleteData) - spec.Parameters[config.LABEL_DELETE_BACKUPS] = strconv.FormatBool(deleteBackups) - spec.Parameters[config.LABEL_IS_REPLICA] = strconv.FormatBool(isReplica) - spec.Parameters[config.LABEL_IS_BACKUP] = strconv.FormatBool(isBackup) - spec.Parameters[config.LABEL_PG_CLUSTER] = clusterName - spec.Parameters[config.LABEL_REPLICA_NAME] = replicaName - spec.Parameters[config.LABEL_PGHA_SCOPE] = clusterPGHAScope - - newInstance := &crv1.Pgtask{ - ObjectMeta: metav1.ObjectMeta{ - Name: taskName, - }, - Spec: spec, - } - newInstance.ObjectMeta.Labels = make(map[string]string) - newInstance.ObjectMeta.Labels[config.LABEL_PG_CLUSTER] = clusterName - newInstance.ObjectMeta.Labels[config.LABEL_RMDATA] = "true" - - _, err = Clientset.CrunchydataV1().Pgtasks(ns).Create(ctx, newInstance, metav1.CreateOptions{}) - if err != nil { - log.Error(err) - return err - } - - return err -} - // IsValidPVC determines if a PVC with the name provided exits func IsValidPVC(pvcName, ns string) bool { ctx := context.TODO() diff --git a/internal/util/cluster.go b/internal/util/cluster.go index c9d59d9bf6..c3b22fbfaf 100644 --- a/internal/util/cluster.go +++ b/internal/util/cluster.go @@ -231,6 +231,45 @@ func CreateBackrestRepoSecrets(clientset kubernetes.Interface, return err } +// CreateRMDataTask is a legacy method that was moved into this file. This +// spawns the "pgo-rmdata" task which cleans up assets related to removing an +// individual instance or a cluster. I cleaned up the code slightly. +func CreateRMDataTask(clientset kubeapi.Interface, clusterName, replicaName, taskName string, deleteBackups, deleteData, isReplica, isBackup bool, ns, clusterPGHAScope string) error { + ctx := context.TODO() + + // create pgtask CRD + task := &crv1.Pgtask{ + ObjectMeta: metav1.ObjectMeta{ + Name: taskName, + Labels: map[string]string{ + config.LABEL_PG_CLUSTER: clusterName, + config.LABEL_RMDATA: "true", + }, + }, + Spec: crv1.PgtaskSpec{ + Name: taskName, + Namespace: ns, + Parameters: map[string]string{ + config.LABEL_DELETE_DATA: strconv.FormatBool(deleteData), + config.LABEL_DELETE_BACKUPS: strconv.FormatBool(deleteBackups), + config.LABEL_IS_REPLICA: strconv.FormatBool(isReplica), + config.LABEL_IS_BACKUP: strconv.FormatBool(isBackup), + config.LABEL_PG_CLUSTER: clusterName, + config.LABEL_REPLICA_NAME: replicaName, + config.LABEL_PGHA_SCOPE: clusterPGHAScope, + }, + TaskType: crv1.PgtaskDeleteData, + }, + } + + if _, err := clientset.CrunchydataV1().Pgtasks(ns).Create(ctx, task, metav1.CreateOptions{}); err != nil { + log.Error(err) + return err + } + + return nil +} + // GenerateNodeAffinity creates a Kubernetes node affinity object suitable for // storage on our custom resource. For now, it only supports preferred affinity, // though can be expanded to support more complex rules From da14a107f3aa8ed76ba6e70becbcd3037316996d Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 10 Jan 2021 10:55:01 -0500 Subject: [PATCH 131/276] Deleting a pgcluster resource deletes a PostgreSQL cluster This behavior, which had been removed circa 4.1, is now reintroduced to use the present machienry when a PostgreSQL cluster is being deleted. Deleting a pgcluster resoruce directly will now delete a PostgreSQL cluster and all of its associated objects. To retain the PVCs for the PostgreSQL data directory or the backups repository, one can set the following Annotations on a pgclusters custom resource: - `keep-backups`: indicates to keep the pgBackRest PVC when deleting the cluster. - `keep-data`: indicates to keep the PostgreSQL data PVC when deleting the cluster. Issue: [ch9435] Issue: #1203 --- cmd/pgo-rmdata/process.go | 20 ++++--- docs/content/custom-resources/_index.md | 9 +++ .../apiserver/clusterservice/clusterimpl.go | 13 ++--- .../apiserver/clusterservice/scaleimpl.go | 9 +-- internal/config/annotations.go | 6 ++ .../pgcluster/pgclustercontroller.go | 58 +++++++++++++++++-- internal/operator/cluster/cluster.go | 48 --------------- internal/operator/task/rmdata.go | 38 +++++------- internal/util/cluster.go | 17 ++++-- 9 files changed, 113 insertions(+), 105 deletions(-) diff --git a/cmd/pgo-rmdata/process.go b/cmd/pgo-rmdata/process.go index f8923a8a12..60b123b84d 100644 --- a/cmd/pgo-rmdata/process.go +++ b/cmd/pgo-rmdata/process.go @@ -101,7 +101,18 @@ func Delete(request Request) { log.Info("rmdata.Process cluster use case") - // first, clear out any of the scheduled jobs that may occur, as this would be + // attempt to delete the pgcluster object if it has not already been deleted. + // quite possibly, we are here because one deleted the pgcluster object + // already, so this step is optional + if _, err := request.Clientset.CrunchydataV1().Pgclusters(request.Namespace).Get( + ctx, request.ClusterName, metav1.GetOptions{}); err == nil { + if err := request.Clientset.CrunchydataV1().Pgclusters(request.Namespace).Delete( + ctx, request.ClusterName, metav1.DeleteOptions{}); err != nil { + log.Error(err) + } + } + + // clear out any of the scheduled jobs that may occur, as this would be // executing asynchronously against any stale data removeSchedules(request) @@ -111,13 +122,8 @@ func Delete(request Request) { removeUserSecrets(request) } - // handle the case of 'pgo delete cluster mycluster' + // remove the cluster Deployments removeCluster(request) - if err := request.Clientset. - CrunchydataV1().Pgclusters(request.Namespace). - Delete(ctx, request.ClusterName, metav1.DeleteOptions{}); err != nil { - log.Error(err) - } removeServices(request) removeAddons(request) removePgreplicas(request) diff --git a/docs/content/custom-resources/_index.md b/docs/content/custom-resources/_index.md index 62ddc459ba..43341006df 100644 --- a/docs/content/custom-resources/_index.md +++ b/docs/content/custom-resources/_index.md @@ -679,6 +679,15 @@ spec: Save your edits, and in a short period of time, you should see these annotations applied to the managed Deployments. +### Delete a PostgreSQL Cluster + +A PostgreSQL cluster can be deleted by simply deleting the `pgclusters.crunchydata.com` resource. + +It is possible to keep both the PostgreSQL data directory as well as the pgBackRest backup repository when using this method by setting the following annotations on the `pgclusters.crunchydata.com` custom resource: + +- `keep-backups`: indicates to keep the pgBackRest PVC when deleting the cluster. +- `keep-data`: indicates to keep the PostgreSQL data PVC when deleting the cluster. + ## PostgreSQL Operator Custom Resource Definitions There are several PostgreSQL Operator Custom Resource Definitions (CRDs) that diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index c68ab5d406..bb42a5dd40 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -83,7 +83,8 @@ func DeleteCluster(name, selector string, deleteData, deleteBackups bool, ns, pg return response } - for _, cluster := range clusterList.Items { + for i := range clusterList.Items { + cluster := &clusterList.Items[i] // check if the current cluster is not upgraded to the deployed // Operator version. If not, do not allow the command to complete @@ -94,22 +95,16 @@ func DeleteCluster(name, selector string, deleteData, deleteBackups bool, ns, pg } log.Debugf("deleting cluster %s", cluster.Spec.Name) - taskName := cluster.Spec.Name + "-rmdata" - log.Debugf("creating taskName %s", taskName) - isBackup := false - isReplica := false - replicaName := "" - clusterPGHAScope := cluster.ObjectMeta.Labels[config.LABEL_PGHA_SCOPE] // first delete any existing rmdata pgtask with the same name - err = apiserver.Clientset.CrunchydataV1().Pgtasks(ns).Delete(ctx, taskName, metav1.DeleteOptions{}) + err = apiserver.Clientset.CrunchydataV1().Pgtasks(ns).Delete(ctx, cluster.Name+"-rmdata", metav1.DeleteOptions{}) if err != nil && !kerrors.IsNotFound(err) { response.Status.Code = msgs.Error response.Status.Msg = err.Error() return response } - if err := util.CreateRMDataTask(apiserver.Clientset, cluster.Spec.Name, replicaName, taskName, deleteBackups, deleteData, isReplica, isBackup, ns, clusterPGHAScope); err != nil { + if err := util.CreateRMDataTask(apiserver.Clientset, cluster, "", deleteBackups, deleteData, false, false); err != nil { log.Debugf("error on creating rmdata task %s", err.Error()) response.Status.Code = msgs.Error response.Status.Msg = err.Error() diff --git a/internal/apiserver/clusterservice/scaleimpl.go b/internal/apiserver/clusterservice/scaleimpl.go index c3426b2f34..dad920a1a3 100644 --- a/internal/apiserver/clusterservice/scaleimpl.go +++ b/internal/apiserver/clusterservice/scaleimpl.go @@ -288,14 +288,7 @@ func ScaleDown(deleteData bool, clusterName, replicaName, ns string) msgs.ScaleD } // create the rmdata task which does the cleanup - - clusterPGHAScope := cluster.ObjectMeta.Labels[config.LABEL_PGHA_SCOPE] - deleteBackups := false - isReplica := true - isBackup := false - taskName := replicaName + "-rmdata" - - if err := util.CreateRMDataTask(apiserver.Clientset, clusterName, replicaName, taskName, deleteBackups, deleteData, isReplica, isBackup, ns, clusterPGHAScope); err != nil { + if err := util.CreateRMDataTask(apiserver.Clientset, cluster, replicaName, false, deleteData, true, false); err != nil { response.Status.Code = msgs.Error response.Status.Msg = err.Error() return response diff --git a/internal/config/annotations.go b/internal/config/annotations.go index 3f42e4e4db..bde7a345f8 100644 --- a/internal/config/annotations.go +++ b/internal/config/annotations.go @@ -21,6 +21,12 @@ const ( ANNOTATION_BACKREST_RESTORE = "pgo-backrest-restore" ANNOTATION_PGHA_BOOTSTRAP_REPLICA = "pgo-pgha-bootstrap-replica" ANNOTATION_PRIMARY_DEPLOYMENT = "primary-deployment" + // ANNOTATION_CLUSTER_KEEP_BACKUPS indicates that if a custom resource is + // deleted, ensure the backups are kept + ANNOTATION_CLUSTER_KEEP_BACKUPS = "keep-backups" + // ANNOTATION_CLUSTER_KEEP_DATA indicates that if a custom resource is + // deleted, ensure the data directory is kept + ANNOTATION_CLUSTER_KEEP_DATA = "keep-data" // annotation to track the cluster's current primary ANNOTATION_CURRENT_PRIMARY = "current-primary" // annotation to indicate whether a cluster has been upgraded diff --git a/internal/controller/pgcluster/pgclustercontroller.go b/internal/controller/pgcluster/pgclustercontroller.go index bd7556ec3a..adecbff0e5 100644 --- a/internal/controller/pgcluster/pgclustercontroller.go +++ b/internal/controller/pgcluster/pgclustercontroller.go @@ -348,11 +348,61 @@ func (c *Controller) onUpdate(oldObj, newObj interface{}) { // onDelete is called when a pgcluster is deleted func (c *Controller) onDelete(obj interface{}) { - // cluster := obj.(*crv1.Pgcluster) - // log.Debugf("[Controller] ns=%s onDelete %s", cluster.ObjectMeta.Namespace, cluster.ObjectMeta.SelfLink) + ctx := context.TODO() + cluster := obj.(*crv1.Pgcluster) + + log.Debugf("pgcluster onDelete for cluster %s (namespace %s)", cluster.Name, cluster.Namespace) + + // a quick guard: see if the "rmdata Job" is running. + options := metav1.ListOptions{ + LabelSelector: fields.AndSelectors( + fields.OneTermEqualSelector(config.LABEL_PG_CLUSTER, cluster.Name), + fields.OneTermEqualSelector(config.LABEL_RMDATA, config.LABEL_TRUE), + ).String(), + } + + jobs, err := c.Client.BatchV1().Jobs(cluster.Namespace).List(ctx, options) - // handle pgcluster cleanup - // clusteroperator.DeleteClusterBase(c.PgclusterClientset, c.PgclusterClient, cluster, cluster.ObjectMeta.Namespace) + if err != nil { + log.Error(err) + return + } + + // iterate through the list of Jobs and see if any are currently active or + // succeeded. + // a succeeded Job could be a remnaint of an old Job for the cluser of a + // same name, in which case, we can continue with deleting the cluster + for _, job := range jobs.Items { + // we will return for one of two reasons: + // 1. if the Job is currently active + // 2. if the Job is not active but never has completed and is below the + // backoff limit -- this could be evidence that the Job is retrying + if job.Status.Active > 0 { + return + } else if job.Status.Succeeded < 1 && job.Status.Failed < *job.Spec.BackoffLimit { + return + } + } + + // we need to create a special pgtask that will create the Job (I know). So + // let's attempt to do that here. First, clear out any other pgtask with this + // existing name. If it errors because it's not found, we're OK + taskName := cluster.Name + "-rmdata" + if err := c.Client.CrunchydataV1().Pgtasks(cluster.Namespace).Delete( + ctx, taskName, metav1.DeleteOptions{}); err != nil && !kerrors.IsNotFound(err) { + log.Error(err) + return + } + + // determine if the data directory or backups should be kept + _, keepBackups := cluster.ObjectMeta.GetAnnotations()[config.ANNOTATION_CLUSTER_KEEP_BACKUPS] + _, keepData := cluster.ObjectMeta.GetAnnotations()[config.ANNOTATION_CLUSTER_KEEP_DATA] + + // create the deletion job. this will delete any data and backups for this + // cluster + if err := util.CreateRMDataTask(c.Client, cluster, "", !keepBackups, !keepData, false, false); err != nil { + log.Error(err) + } } // AddPGClusterEventHandler adds the pgcluster event handler to the pgcluster informer diff --git a/internal/operator/cluster/cluster.go b/internal/operator/cluster/cluster.go index 05455daaf7..1a96e004a1 100644 --- a/internal/operator/cluster/cluster.go +++ b/internal/operator/cluster/cluster.go @@ -339,37 +339,6 @@ func AddBootstrapRepo(clientset kubernetes.Interface, cluster *crv1.Pgcluster) ( return } -// DeleteClusterBase ... -func DeleteClusterBase(clientset kubernetes.Interface, cl *crv1.Pgcluster, namespace string) { - _ = DeleteCluster(clientset, cl, namespace) - - // delete any existing configmaps - if err := deleteConfigMaps(clientset, cl.Spec.Name, namespace); err != nil { - log.Error(err) - } - - // delete any existing pgtasks ??? - - // publish delete cluster event - topics := make([]string, 1) - topics[0] = events.EventTopicCluster - - f := events.EventDeleteClusterFormat{ - EventHeader: events.EventHeader{ - Namespace: namespace, - Username: cl.ObjectMeta.Labels[config.LABEL_PGOUSER], - Topic: topics, - Timestamp: time.Now(), - EventType: events.EventDeleteCluster, - }, - Clustername: cl.Spec.Name, - } - - if err := events.Publish(f); err != nil { - log.Error(err) - } -} - // ScaleBase ... func ScaleBase(clientset kubeapi.Interface, replica *crv1.Pgreplica, namespace string) { ctx := context.TODO() @@ -759,23 +728,6 @@ func createMissingUserSecrets(clientset kubernetes.Interface, cluster *crv1.Pgcl return createMissingUserSecret(clientset, cluster, cluster.Spec.User) } -func deleteConfigMaps(clientset kubernetes.Interface, clusterName, ns string) error { - ctx := context.TODO() - label := fmt.Sprintf("pg-cluster=%s", clusterName) - list, err := clientset.CoreV1().ConfigMaps(ns).List(ctx, metav1.ListOptions{LabelSelector: label}) - if err != nil { - return fmt.Errorf("No configMaps found for selector: %s", label) - } - - for _, configmap := range list.Items { - err := clientset.CoreV1().ConfigMaps(ns).Delete(ctx, configmap.Name, metav1.DeleteOptions{}) - if err != nil { - return err - } - } - return nil -} - func publishClusterCreateFailure(cl *crv1.Pgcluster, errorMsg string) { pgouser := cl.ObjectMeta.Labels[config.LABEL_PGOUSER] topics := make([]string, 1) diff --git a/internal/operator/task/rmdata.go b/internal/operator/task/rmdata.go index d65141b207..7f855ecf2e 100644 --- a/internal/operator/task/rmdata.go +++ b/internal/operator/task/rmdata.go @@ -83,13 +83,6 @@ func RemoveData(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask return } - // if the clustername is not empty, get the pgcluster - cluster, err := clientset.CrunchydataV1().Pgclusters(namespace).Get(ctx, clusterName, metav1.GetOptions{}) - if err != nil { - log.Error(err) - return - } - jobName := clusterName + "-rmdata-" + util.RandStringBytesRmndr(4) jobFields := rmdatajobTemplateFields{ @@ -102,40 +95,39 @@ func RemoveData(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask RemoveBackup: removeBackup, IsReplica: isReplica, IsBackup: isBackup, - PGOImagePrefix: util.GetValueOrDefault(cluster.Spec.PGOImagePrefix, operator.Pgo.Pgo.PGOImagePrefix), + PGOImagePrefix: util.GetValueOrDefault(task.Spec.Parameters[config.LABEL_IMAGE_PREFIX], operator.Pgo.Pgo.PGOImagePrefix), PGOImageTag: operator.Pgo.Pgo.PGOImageTag, SecurityContext: operator.GetPodSecurityContext(task.Spec.StorageSpec.GetSupplementalGroups()), } - log.Debugf("creating rmdata job %s for cluster %s ", jobName, task.Spec.Name) - var doc2 bytes.Buffer - err = config.RmdatajobTemplate.Execute(&doc2, jobFields) - if err != nil { - log.Error(err.Error()) - return - } + log.Debugf("creating rmdata job %s for cluster %s ", jobName, task.Spec.Name) if operator.CRUNCHY_DEBUG { _ = config.RmdatajobTemplate.Execute(os.Stdout, jobFields) } - newjob := v1batch.Job{} - err = json.Unmarshal(doc2.Bytes(), &newjob) - if err != nil { + doc := bytes.Buffer{} + if err := config.RmdatajobTemplate.Execute(&doc, jobFields); err != nil { + log.Error(err) + return + } + + job := v1batch.Job{} + if err := json.Unmarshal(doc.Bytes(), &job); err != nil { log.Error("error unmarshalling json into Job " + err.Error()) return } // set the container image to an override value, if one exists operator.SetContainerImageOverride(config.CONTAINER_IMAGE_PGO_RMDATA, - &newjob.Spec.Template.Spec.Containers[0]) + &job.Spec.Template.Spec.Containers[0]) - j, err := clientset.BatchV1().Jobs(namespace).Create(ctx, &newjob, metav1.CreateOptions{}) - if err != nil { - log.Errorf("got error when creating rmdata job %s", newjob.Name) + if _, err := clientset.BatchV1().Jobs(namespace).Create(ctx, &job, metav1.CreateOptions{}); err != nil { + log.Error(err) return } - log.Debugf("successfully created rmdata job %s", j.Name) + + log.Debugf("successfully created rmdata job %s", job.Name) publishDeleteCluster(task.Spec.Parameters[config.LABEL_PG_CLUSTER], task.ObjectMeta.Labels[config.LABEL_PGOUSER], namespace) diff --git a/internal/util/cluster.go b/internal/util/cluster.go index c3b22fbfaf..364ce77993 100644 --- a/internal/util/cluster.go +++ b/internal/util/cluster.go @@ -234,35 +234,40 @@ func CreateBackrestRepoSecrets(clientset kubernetes.Interface, // CreateRMDataTask is a legacy method that was moved into this file. This // spawns the "pgo-rmdata" task which cleans up assets related to removing an // individual instance or a cluster. I cleaned up the code slightly. -func CreateRMDataTask(clientset kubeapi.Interface, clusterName, replicaName, taskName string, deleteBackups, deleteData, isReplica, isBackup bool, ns, clusterPGHAScope string) error { +func CreateRMDataTask(clientset kubeapi.Interface, cluster *crv1.Pgcluster, replicaName string, deleteBackups, deleteData, isReplica, isBackup bool) error { ctx := context.TODO() + taskName := cluster.Name + "-rmdata" + if replicaName != "" { + taskName = replicaName + "-rmdata" + } // create pgtask CRD task := &crv1.Pgtask{ ObjectMeta: metav1.ObjectMeta{ Name: taskName, Labels: map[string]string{ - config.LABEL_PG_CLUSTER: clusterName, + config.LABEL_PG_CLUSTER: cluster.Name, config.LABEL_RMDATA: "true", }, }, Spec: crv1.PgtaskSpec{ Name: taskName, - Namespace: ns, + Namespace: cluster.Namespace, Parameters: map[string]string{ config.LABEL_DELETE_DATA: strconv.FormatBool(deleteData), config.LABEL_DELETE_BACKUPS: strconv.FormatBool(deleteBackups), + config.LABEL_IMAGE_PREFIX: cluster.Spec.PGOImagePrefix, config.LABEL_IS_REPLICA: strconv.FormatBool(isReplica), config.LABEL_IS_BACKUP: strconv.FormatBool(isBackup), - config.LABEL_PG_CLUSTER: clusterName, + config.LABEL_PG_CLUSTER: cluster.Name, config.LABEL_REPLICA_NAME: replicaName, - config.LABEL_PGHA_SCOPE: clusterPGHAScope, + config.LABEL_PGHA_SCOPE: cluster.ObjectMeta.GetLabels()[config.LABEL_PGHA_SCOPE], }, TaskType: crv1.PgtaskDeleteData, }, } - if _, err := clientset.CrunchydataV1().Pgtasks(ns).Create(ctx, task, metav1.CreateOptions{}); err != nil { + if _, err := clientset.CrunchydataV1().Pgtasks(cluster.Namespace).Create(ctx, task, metav1.CreateOptions{}); err != nil { log.Error(err) return err } From 2e0e21b17334f8c4eb66a82f37303d4b41c44092 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 10 Jan 2021 14:51:14 -0500 Subject: [PATCH 132/276] Remove cluster identifier label This is not referenced anywhere. --- internal/apiserver/backrestservice/backrestimpl.go | 5 +---- internal/apiserver/clusterservice/scaleimpl.go | 1 - internal/config/labels.go | 11 +++++------ internal/controller/pgcluster/pgclustercontroller.go | 12 ------------ internal/controller/pgtask/backresthandler.go | 3 +-- internal/operator/backrest/backup.go | 2 -- internal/operator/backrest/restore.go | 2 +- internal/operator/cluster/clusterlogic.go | 1 - 8 files changed, 8 insertions(+), 29 deletions(-) diff --git a/internal/apiserver/backrestservice/backrestimpl.go b/internal/apiserver/backrestservice/backrestimpl.go index ec1e9b2f5b..b4b14830f9 100644 --- a/internal/apiserver/backrestservice/backrestimpl.go +++ b/internal/apiserver/backrestservice/backrestimpl.go @@ -217,7 +217,6 @@ func CreateBackup(request *msgs.CreateBackrestBackupRequest, ns, pgouser string) _, err = apiserver.Clientset.CrunchydataV1().Pgtasks(ns).Create(ctx, getBackupParams( - cluster.ObjectMeta.Labels[config.LABEL_PG_CLUSTER_IDENTIFIER], clusterName, taskName, crv1.PgtaskBackrestBackup, podname, "database", util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, apiserver.Pgo.Cluster.CCPImagePrefix), request.BackupOpts, request.BackrestStorageType, operator.GetS3VerifyTLSSetting(cluster), jobName, ns, pgouser), @@ -283,7 +282,7 @@ func DeleteBackup(request msgs.DeleteBackrestBackupRequest) msgs.DeleteBackrestB return response } -func getBackupParams(identifier, clusterName, taskName, action, podName, containerName, imagePrefix, backupOpts, backrestStorageType, s3VerifyTLS, jobName, ns, pgouser string) *crv1.Pgtask { +func getBackupParams(clusterName, taskName, action, podName, containerName, imagePrefix, backupOpts, backrestStorageType, s3VerifyTLS, jobName, ns, pgouser string) *crv1.Pgtask { var newInstance *crv1.Pgtask spec := crv1.PgtaskSpec{} spec.Name = taskName @@ -311,7 +310,6 @@ func getBackupParams(identifier, clusterName, taskName, action, podName, contain } newInstance.ObjectMeta.Labels = make(map[string]string) newInstance.ObjectMeta.Labels[config.LABEL_PG_CLUSTER] = clusterName - newInstance.ObjectMeta.Labels[config.LABEL_PG_CLUSTER_IDENTIFIER] = identifier newInstance.ObjectMeta.Labels[config.LABEL_PGOUSER] = pgouser return newInstance } @@ -569,7 +567,6 @@ func Restore(request *msgs.RestoreRequest, ns, pgouser string) msgs.RestoreRespo return resp } - pgtask.ObjectMeta.Labels[config.LABEL_PG_CLUSTER_IDENTIFIER] = cluster.ObjectMeta.Labels[config.LABEL_PG_CLUSTER_IDENTIFIER] pgtask.ObjectMeta.Labels[config.LABEL_PGOUSER] = pgouser pgtask.Spec.Parameters[crv1.PgtaskWorkflowID] = id diff --git a/internal/apiserver/clusterservice/scaleimpl.go b/internal/apiserver/clusterservice/scaleimpl.go index dad920a1a3..6115d538f3 100644 --- a/internal/apiserver/clusterservice/scaleimpl.go +++ b/internal/apiserver/clusterservice/scaleimpl.go @@ -119,7 +119,6 @@ func ScaleCluster(request msgs.ClusterScaleRequest, pgouser string) msgs.Cluster spec.Tolerations = request.Tolerations labels[config.LABEL_PGOUSER] = pgouser - labels[config.LABEL_PG_CLUSTER_IDENTIFIER] = cluster.ObjectMeta.Labels[config.LABEL_PG_CLUSTER_IDENTIFIER] for i := 0; i < request.ReplicaCount; i++ { uniqueName := util.RandStringBytesRmndr(4) diff --git a/internal/config/labels.go b/internal/config/labels.go index 327eb74183..d07fdd3219 100644 --- a/internal/config/labels.go +++ b/internal/config/labels.go @@ -17,12 +17,11 @@ package config // resource labels used by the operator const ( - LABEL_NAME = "name" - LABEL_SELECTOR = "selector" - LABEL_OPERATOR = "postgres-operator" - LABEL_PG_CLUSTER = "pg-cluster" - LABEL_PG_CLUSTER_IDENTIFIER = "pg-cluster-id" - LABEL_PG_DATABASE = "pgo-pg-database" + LABEL_NAME = "name" + LABEL_SELECTOR = "selector" + LABEL_OPERATOR = "postgres-operator" + LABEL_PG_CLUSTER = "pg-cluster" + LABEL_PG_DATABASE = "pgo-pg-database" ) const LABEL_PGTASK = "pg-task" diff --git a/internal/controller/pgcluster/pgclustercontroller.go b/internal/controller/pgcluster/pgclustercontroller.go index adecbff0e5..576a5d0af9 100644 --- a/internal/controller/pgcluster/pgclustercontroller.go +++ b/internal/controller/pgcluster/pgclustercontroller.go @@ -18,7 +18,6 @@ limitations under the License. import ( "context" "encoding/json" - "io/ioutil" "reflect" "strings" @@ -118,8 +117,6 @@ func (c *Controller) processNextItem() bool { return true } - addIdentifier(cluster) - // If bootstrapping from an existing data source then attempt to create the pgBackRest repository. // If a repo already exists (e.g. because it is associated with a currently running cluster) then // proceed with bootstrapping. @@ -416,15 +413,6 @@ func (c *Controller) AddPGClusterEventHandler() { log.Debugf("pgcluster Controller: added event handler to informer") } -func addIdentifier(clusterCopy *crv1.Pgcluster) { - u, err := ioutil.ReadFile("/proc/sys/kernel/random/uuid") - if err != nil { - log.Error(err) - } - - clusterCopy.ObjectMeta.Labels[config.LABEL_PG_CLUSTER_IDENTIFIER] = string(u[:len(u)-1]) -} - // updateAnnotations updates any custom annitations that may be on the managed // deployments, which includes: // diff --git a/internal/controller/pgtask/backresthandler.go b/internal/controller/pgtask/backresthandler.go index f1aff229df..6cbcbff8f8 100644 --- a/internal/controller/pgtask/backresthandler.go +++ b/internal/controller/pgtask/backresthandler.go @@ -56,8 +56,7 @@ func (c *Controller) handleBackrestRestore(task *crv1.Pgtask) { } log.Debugf("pgtask Controller: added restore job for cluster %s", clusterName) - backrestoperator.PublishRestore(cluster.ObjectMeta.Labels[config.LABEL_PG_CLUSTER_IDENTIFIER], - clusterName, task.ObjectMeta.Labels[config.LABEL_PGOUSER], namespace) + backrestoperator.PublishRestore(clusterName, task.ObjectMeta.Labels[config.LABEL_PGOUSER], namespace) err = backrestoperator.UpdateWorkflow(c.Client, task.Spec.Parameters[crv1.PgtaskWorkflowID], namespace, crv1.PgtaskWorkflowBackrestRestoreJobCreatedStatus) diff --git a/internal/operator/backrest/backup.go b/internal/operator/backrest/backup.go index efdd97f7e9..4d129b60ac 100644 --- a/internal/operator/backrest/backup.go +++ b/internal/operator/backrest/backup.go @@ -148,7 +148,6 @@ func Backrest(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask) &newjob.Spec.Template.Spec.Containers[0]) newjob.ObjectMeta.Labels[config.LABEL_PGOUSER] = task.ObjectMeta.Labels[config.LABEL_PGOUSER] - newjob.ObjectMeta.Labels[config.LABEL_PG_CLUSTER_IDENTIFIER] = task.ObjectMeta.Labels[config.LABEL_PG_CLUSTER_IDENTIFIER] backupType := task.Spec.Parameters[config.LABEL_PGHA_BACKUP_TYPE] if backupType != "" { @@ -242,7 +241,6 @@ func CreateBackup(clientset pgo.Interface, namespace, clusterName, podName strin } newInstance.ObjectMeta.Labels = make(map[string]string) newInstance.ObjectMeta.Labels[config.LABEL_PG_CLUSTER] = cluster.Name - newInstance.ObjectMeta.Labels[config.LABEL_PG_CLUSTER_IDENTIFIER] = cluster.ObjectMeta.Labels[config.LABEL_PG_CLUSTER_IDENTIFIER] newInstance.ObjectMeta.Labels[config.LABEL_PGOUSER] = cluster.ObjectMeta.Labels[config.LABEL_PGOUSER] _, err = clientset.CrunchydataV1().Pgtasks(cluster.Namespace).Create(ctx, newInstance, metav1.CreateOptions{}) diff --git a/internal/operator/backrest/restore.go b/internal/operator/backrest/restore.go index 4ff7797cda..92fdd9fd04 100644 --- a/internal/operator/backrest/restore.go +++ b/internal/operator/backrest/restore.go @@ -317,7 +317,7 @@ func UpdateWorkflow(clientset pgo.Interface, workflowID, namespace, status strin } // PublishRestore is responsible for publishing the 'RestoreCluster' event for a restore -func PublishRestore(id, clusterName, username, namespace string) { +func PublishRestore(clusterName, username, namespace string) { topics := make([]string, 1) topics[0] = events.EventTopicCluster diff --git a/internal/operator/cluster/clusterlogic.go b/internal/operator/cluster/clusterlogic.go index 528ff775dc..90726ec172 100644 --- a/internal/operator/cluster/clusterlogic.go +++ b/internal/operator/cluster/clusterlogic.go @@ -275,7 +275,6 @@ func getClusterDeploymentFields(clientset kubernetes.Interface, } cl.Spec.UserLabels[config.LABEL_PGOUSER] = cl.ObjectMeta.Labels[config.LABEL_PGOUSER] - cl.Spec.UserLabels[config.LABEL_PG_CLUSTER_IDENTIFIER] = cl.ObjectMeta.Labels[config.LABEL_PG_CLUSTER_IDENTIFIER] // Set the Patroni scope to the name of the primary deployment. Replicas will get scope using the // 'crunchy-pgha-scope' label on the pgcluster From e89cd9dbc7d953d8ac0f3f13a6bdceba17727d24 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 11 Jan 2021 11:03:55 -0500 Subject: [PATCH 133/276] Tighten up ephemeral storage limits This removes an ephemeral storage volume that is no longer used and places a cap of 64Mi on the pgBadger ephemeral storage is not mounted unless there is a pgBadger instance directory. This does not add a size limit to shared memory (dshm), as that is a much more complicated topic. Issue: #2188 --- .../files/pgo-configs/cluster-deployment.json | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json index 0e3f2ef6cc..e9f0367b8e 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json @@ -176,9 +176,6 @@ }, { "mountPath": "/pgconf", "name": "pgconf-volume" - }, { - "mountPath": "/recover", - "name": "recover-volume" }, { "mountPath": "/dev/shm", @@ -282,11 +279,11 @@ {{ end }} {{ end }} { - "name": "recover-volume", - "emptyDir": { "medium": "Memory" } - }, { "name": "report", - "emptyDir": { "medium": "Memory" } + "emptyDir": { + "medium": "Memory", + "sizeLimit": "64Mi" + } }, { "name": "dshm", From 6a90c80451aadf085f0f703e85d3bbe7bc81d7fa Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 11 Jan 2021 11:42:23 -0500 Subject: [PATCH 134/276] Update Helm example This reduces the number of requirements that are needed to deploy a PostgreSQL cluster with this Helm chart. It does add additional attributes to help further customize the PostgreSQL cluster, as well as updated instructions for how to run the example. --- examples/helm/README.md | 87 +++++++++++++------ .../templates/hippo-secret.yaml | 12 --- .../create-cluster/templates/pgcluster.yaml | 65 -------------- .../templates/postgres-secret.yaml | 12 --- examples/helm/create-cluster/values.yaml | 15 ---- .../{create-cluster => postgres}/.helmignore | 0 .../{create-cluster => postgres}/Chart.yaml | 6 +- .../templates/NOTES.txt | 26 +++++- .../templates/_helpers.tpl | 0 .../helm/postgres/templates/pgcluster.yaml | 62 +++++++++++++ .../helm/postgres/templates/user-secret.yaml | 12 +++ examples/helm/postgres/values.yaml | 14 +++ 12 files changed, 174 insertions(+), 137 deletions(-) delete mode 100644 examples/helm/create-cluster/templates/hippo-secret.yaml delete mode 100644 examples/helm/create-cluster/templates/pgcluster.yaml delete mode 100644 examples/helm/create-cluster/templates/postgres-secret.yaml delete mode 100644 examples/helm/create-cluster/values.yaml rename examples/helm/{create-cluster => postgres}/.helmignore (100%) rename examples/helm/{create-cluster => postgres}/Chart.yaml (88%) rename examples/helm/{create-cluster => postgres}/templates/NOTES.txt (64%) rename examples/helm/{create-cluster => postgres}/templates/_helpers.tpl (100%) create mode 100644 examples/helm/postgres/templates/pgcluster.yaml create mode 100644 examples/helm/postgres/templates/user-secret.yaml create mode 100644 examples/helm/postgres/values.yaml diff --git a/examples/helm/README.md b/examples/helm/README.md index 390bfbbaae..81fbb97fe8 100644 --- a/examples/helm/README.md +++ b/examples/helm/README.md @@ -1,29 +1,20 @@ -# create-cluster +# Create a Postgres Cluster -This is a working example of how to create a cluster via the crd workflow -using a [Helm](https://helm.sh/) chart. +This is a working example of how to create a PostgreSQL cluster [Helm](https://helm.sh/) chart. ## Prerequisites ### Postgres Operator -This example assumes you have the Crunchy PostgreSQL Operator installed -in a namespace called `pgo`. +This example assumes you have the [Crunchy PostgreSQL Operator installed](https://access.crunchydata.com/documentation/postgres-operator/latest/quickstart/) in a namespace called `pgo`. ### Helm -Helm will also need to be installed for this example to run - -## Documentation - -Please see the documentation for more guidance using custom resources: - -https://access.crunchydata.com/documentation/postgres-operator/latest/custom-resources/ +To execute a Helm chart, [Helm](https://helm.sh/) needs to be installed in your local environment. ## Setup -If you are running Postgres Operator 4.5.1 or later, you can skip the below -step. +If you are running Postgres Operator 4.5.1 or later, you can skip the step below. ### Before 4.5.1 @@ -42,38 +33,80 @@ ssh-keygen -t ed25519 -N '' -f "${pgo_cluster_name}-key" ## Running the Example -For this example we will deploy the cluster into the `pgo` namespace where the -Postgres Operator is installed and running. +### Download the Helm Chart -Return to the `create-cluster` directory: +For this example we will deploy the cluster into the `pgo` namespace where the Postgres Operator is installed and running. + +You will need to download this Helm chart. One way to do this is by cloning the Postgres Operator project into your local environment: ``` -cd postgres-operator/examples/helm/create-cluster +git clone https://github.com/CrunchyData/postgres-operator.git +``` + +Go into the directory that contains the Helm chart for creating a PostgreSQL cluster: + ``` +cd postgres-operator/examples/helm +``` + +### Set Values + +There are only three required values to run the Helm chart: + +- `name`: The name of your PostgreSQL cluster. +- `namespace`: The namespace for where the PostgreSQL cluster should be deployed. +- `password`: A password for the user that will be allowed to connect to the database. + +The following values can also be set: + +- `cpu`: The CPU limit for the PostgreSQL cluster. Follows standard Kubernetes formatting. +- `diskSize`: The size of the PVC for the PostgreSQL cluster. Follows standard Kubernetes formatting. +- `ha`: Whether or not to deploy a high availability PostgreSQL cluster. Can be either `true` or `false`, defaults to `false`. +- `imagePrefix`: The prefix of the container images to use for this PostgreSQL cluster. Default to `registry.developers.crunchydata.com/crunchydata`. +- `image`: The name of the container image to use for the PostgreSQL cluster. Defaults to `crunchy-postgres-ha`. +- `imageTag`: The container image tag to use. Defaults to `centos8-13.1-4.6.0-beta.2`. +- `memory`: The memory limit for the PostgreSQL cluster. Follows standard Kubernetes formatting. +- `monitoring`: Whether or not to enable monitoring / metrics collection for this PostgreSQL instance. Can either be `true` or `false`, defaults to `false`. + +### Execute the Chart The following commands will allow you to execute a dry run first with debug if you want to verify everything is set correctly. Then after everything looks good run the install command with out the flags: ``` -helm install --dry-run --debug postgres-operator-create-cluster . -n pgo -helm install postgres-operator-create-cluster . -n pgo +helm install -n pgo --dry-run --debug postgres-cluster postgres +helm install -n pgo postgres-cluster postgres ``` +This will deploy a PostgreSQL cluster with the specified name into the specified namespace. + ## Verify -Now you can your Hippo cluster has deployed into the pgo namespace by running -these few commands: +You can verify that your PostgreSQL cluster is deployed into the `pgo` namespace by running the following commands: ``` kubectl get all -n pgo +``` -pgo test hippo -n pgo +Once your PostgreSQL cluster is provisioned, you can connect to it. Assuming you are using the default value of `hippo` for the name of the cluster, in a new terminal window, set up a port forward to the PostgreSQL cluster: -pgo show cluster hippo -n pgo ``` +kubectl -n pgo port-forward svc/hippo 5432:5432 +``` + +Still assuming your are using the default values for this Helm chart, you can connect to the Postgres cluster with the following command: -## NOTE +``` +PGPASSWORD="W4tch0ut4hippo$" psql -h localhost -U hippo hippo +``` + +## Notes + +Prior to PostgreSQL Operator 4.6.0, you will have to manually clean up some of the artifacts when running `helm uninstall`. + +## Additional Resources + +Please see the documentation for more guidance using custom resources: -As of operator version 4.5.0 when using helm uninstall you will have to manually -clean up some left over artifacts after running the uninstall. +[https://access.crunchydata.com/documentation/postgres-operator/latest/custom-resources/](https://access.crunchydata.com/documentation/postgres-operator/latest/custom-resources/) diff --git a/examples/helm/create-cluster/templates/hippo-secret.yaml b/examples/helm/create-cluster/templates/hippo-secret.yaml deleted file mode 100644 index 8e922196e1..0000000000 --- a/examples/helm/create-cluster/templates/hippo-secret.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: v1 -data: - password: {{ .Values.hipposecretpassword | b64enc }} - username: {{ .Values.hipposecretuser | b64enc }} -kind: Secret -metadata: - labels: - pg-cluster: {{ .Values.pgclustername }} - vendor: crunchydata - name: {{ .Values.pgclustername }}-hippo-secret - namespace: {{ .Values.namespace }} -type: Opaque diff --git a/examples/helm/create-cluster/templates/pgcluster.yaml b/examples/helm/create-cluster/templates/pgcluster.yaml deleted file mode 100644 index eb609def15..0000000000 --- a/examples/helm/create-cluster/templates/pgcluster.yaml +++ /dev/null @@ -1,65 +0,0 @@ -apiVersion: crunchydata.com/v1 -kind: Pgcluster -metadata: - annotations: - current-primary: {{ .Values.pgclustername }} - labels: - autofail: "true" - crunchy-pgbadger: "false" - crunchy-pgha-scope: {{ .Values.pgclustername }} - deployment-name: {{ .Values.pgclustername }} - name: {{ .Values.pgclustername }} - pg-cluster: {{ .Values.pgclustername }} - pgo-version: 4.6.0-beta.2 - pgouser: admin - name: {{ .Values.pgclustername }} - namespace: {{ .Values.namespace }} -spec: - BackrestStorage: - accessmode: ReadWriteOnce - matchLabels: "" - name: "" - size: 3G - storageclass: "" - storagetype: dynamic - supplementalgroups: "" - PrimaryStorage: - accessmode: ReadWriteOnce - matchLabels: "" - name: {{ .Values.pgclustername }} - size: 3G - storageclass: "" - storagetype: dynamic - supplementalgroups: "" - ReplicaStorage: - accessmode: ReadWriteOnce - matchLabels: "" - name: "" - size: 3G - storageclass: "" - storagetype: dynamic - supplementalgroups: "" - annotations: {} - ccpimage: {{ .Values.ccpimage }} - ccpimageprefix: {{ .Values.ccpimageprefix }} - ccpimagetag: {{ .Values.ccpimagetag }} - clustername: {{ .Values.pgclustername }} - database: {{ .Values.pgclustername }} - exporter: false - exporterport: "9187" - limits: {} - name: {{ .Values.pgclustername }} - namespace: {{ .Values.namespace }} - pgDataSource: - restoreFrom: "" - restoreOpts: "" - pgbadgerport: "10000" - pgoimageprefix: {{ .Values.pgoimageprefix }} - podAntiAffinity: - default: preferred - pgBackRest: preferred - pgBouncer: preferred - port: "5432" - user: hippo - userlabels: - pgo-version: {{ .Values.pgoversion }} diff --git a/examples/helm/create-cluster/templates/postgres-secret.yaml b/examples/helm/create-cluster/templates/postgres-secret.yaml deleted file mode 100644 index 914da77e1c..0000000000 --- a/examples/helm/create-cluster/templates/postgres-secret.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: v1 -data: - password: {{ .Values.postgressecretpassword | b64enc }} - username: {{ .Values.postgressecretuser | b64enc }} -kind: Secret -metadata: - labels: - pg-cluster: {{ .Values.pgclustername }} - vendor: crunchydata - name: {{ .Values.pgclustername }}-postgres-secret - namespace: {{ .Values.namespace }} -type: Opaque \ No newline at end of file diff --git a/examples/helm/create-cluster/values.yaml b/examples/helm/create-cluster/values.yaml deleted file mode 100644 index bfc9b73bb0..0000000000 --- a/examples/helm/create-cluster/values.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# Default values for pg_deployment in SDX. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. -# The values is for the namespace and the postgresql cluster name -ccpimage: crunchy-postgres-ha -ccpimageprefix: registry.developers.crunchydata.com/crunchydata -ccpimagetag: centos8-13.1-4.6.0-beta.2 -namespace: pgo -pgclustername: hippo -pgoimageprefix: registry.developers.crunchydata.com/crunchydata -pgoversion: 4.6.0-beta.2 -hipposecretuser: "hippo" -hipposecretpassword: "Supersecurepassword*" -postgressecretuser: "postgres" -postgressecretpassword: "Anothersecurepassword*" diff --git a/examples/helm/create-cluster/.helmignore b/examples/helm/postgres/.helmignore similarity index 100% rename from examples/helm/create-cluster/.helmignore rename to examples/helm/postgres/.helmignore diff --git a/examples/helm/create-cluster/Chart.yaml b/examples/helm/postgres/Chart.yaml similarity index 88% rename from examples/helm/create-cluster/Chart.yaml rename to examples/helm/postgres/Chart.yaml index 5857415edb..d2c7a63902 100644 --- a/examples/helm/create-cluster/Chart.yaml +++ b/examples/helm/postgres/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v2 name: crunchycrdcluster -description: A Helm chart for Kubernetes +description: Helm chart for deploying a PostgreSQL cluster with the Crunchy PostgreSQL Operator # A chart can be either an 'application' or a 'library' chart. # @@ -15,9 +15,9 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.0 +version: 0.2.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. -appVersion: 1.16.0 +appVersion: 4.6.0-beta.2 diff --git a/examples/helm/create-cluster/templates/NOTES.txt b/examples/helm/postgres/templates/NOTES.txt similarity index 64% rename from examples/helm/create-cluster/templates/NOTES.txt rename to examples/helm/postgres/templates/NOTES.txt index 542443a66e..4a3e324405 100644 --- a/examples/helm/create-cluster/templates/NOTES.txt +++ b/examples/helm/postgres/templates/NOTES.txt @@ -1,5 +1,3 @@ -Thank you deploying a crunchy postgreSQL cluster v{{ .Chart.AppVersion }}! - (((((((((((((((((((((( (((((((((((((%%%%%%%((((((((((((((( (((((((((((%%% %%%%(((((((((((( @@ -30,5 +28,27 @@ Thank you deploying a crunchy postgreSQL cluster v{{ .Chart.AppVersion }}! ####%%% %%%%% % %% %%%% +Thank you deploying a Crunchy PostgreSQL cluster v{{ .Chart.AppVersion }}! + +When your cluster has finished deploying, you can connect to it with the +following credentials: + + Username: {{ if .Values.username }}{{ .Values.username }}{{- else }}{{ .Values.name }}{{- end }} + Password: {{ .Values.password }} + +To connect to your PostgreSQL cluster, you can set up a port forward to your +local machine in a separate terminal window: + + kubectl -n {{ .Values.namespace }} port-forward svc/{{ .Values.name }} 5432:5432 + +And use the following connection string to connect to your cluster: + + PGPASSWORD="{{ .Values.password }}" psql -h localhost -U {{ if .Values.username }}{{ .Values.username }}{{- else }}{{ .Values.name }}{{- end }} {{ .Values.name }} + +If you need to log in as the PostgreSQL superuser, you can do so with the following command: + + PGPASSWORD=$(kubectl -n jkatz get secrets {{ .Values.name }}-postgres-secret -o jsonpath='{.data.password}' | base64 -d) psql -h localhost -U postgres {{ .Values.name }} + More information about the custom resource workflow the docs can be found here: -https://access.crunchydata.com/documentation/postgres-operator/latest/custom-resources/ + + https://access.crunchydata.com/documentation/postgres-operator/latest/custom-resources/ diff --git a/examples/helm/create-cluster/templates/_helpers.tpl b/examples/helm/postgres/templates/_helpers.tpl similarity index 100% rename from examples/helm/create-cluster/templates/_helpers.tpl rename to examples/helm/postgres/templates/_helpers.tpl diff --git a/examples/helm/postgres/templates/pgcluster.yaml b/examples/helm/postgres/templates/pgcluster.yaml new file mode 100644 index 0000000000..563d7246de --- /dev/null +++ b/examples/helm/postgres/templates/pgcluster.yaml @@ -0,0 +1,62 @@ +apiVersion: crunchydata.com/v1 +kind: Pgcluster +metadata: + annotations: + current-primary: {{ .Values.name | quote }} + labels: + crunchy-pgha-scope: {{ .Values.name | quote }} + deployment-name: {{ .Values.name | quote }} + name: {{ .Values.name | quote }} + pg-cluster: {{ .Values.name | quote }} + pgo-version: {{ .Chart.AppVersion | quote }} + pgouser: admin + name: {{ .Values.name | quote }} + namespace: {{ .Values.namespace | quote }} +spec: + BackrestStorage: + accessmode: ReadWriteOnce + size: {{ .Values.diskSize | default "2Gi" | quote }} + storagetype: dynamic + PrimaryStorage: + accessmode: ReadWriteOnce + name: {{ .Values.name | quote }} + size: {{ .Values.diskSize | default "1Gi" | quote }} + storagetype: dynamic + ReplicaStorage: + accessmode: ReadWriteOnce + size: {{ .Values.diskSize | default "1Gi" | quote }} + storagetype: dynamic + ccpimage: {{ .Values.image | default "crunchy-postgres-ha" | quote }} + ccpimageprefix: {{ .Values.imagePrefix | default "registry.developers.crunchydata.com/crunchydata" | quote }} + ccpimagetag: {{ .Values.imageTag | default "centos8-13.1-4.6.0-beta.2" | quote }} + clustername: {{ .Values.name | quote }} + database: {{ .Values.name | quote }} + {{- if .Values.monitoring }} + exporter: true + {{- end }} + exporterport: "9187" + limits: + cpu: {{ .Values.cpu | default "0.25" | quote }} + memory: {{ .Values.memory | default "1Gi" | quote }} + name: {{ .Values.name | quote }} + namespace: {{ .Values.namespace | quote }} + pgDataSource: + restoreFrom: "" + restoreOpts: "" + pgbadgerport: "10000" + pgoimageprefix: {{ .Values.imagePrefix | default "registry.developers.crunchydata.com/crunchydata" | quote }} + podAntiAffinity: + default: preferred + pgBackRest: preferred + pgBouncer: preferred + port: "5432" + {{- if .Values.ha }} + replicas: "1" + {{- end }} + {{- if .Values.username }} + user: {{ .Values.username | quote }} + {{- else }} + user: {{ .Values.name | quote }} + {{ end }} + userlabels: + pgo-version: {{ .Chart.AppVersion | quote }} diff --git a/examples/helm/postgres/templates/user-secret.yaml b/examples/helm/postgres/templates/user-secret.yaml new file mode 100644 index 0000000000..b44d31743d --- /dev/null +++ b/examples/helm/postgres/templates/user-secret.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + labels: + pg-cluster: {{ .Values.name | quote }} + vendor: crunchydata + name: {{ .Values.name }}-{{- if .Values.username }}{{ .Values.username }}{{- else }}{{ .Values.name }}{{- end }}-secret + namespace: {{ .Values.namespace | quote }} +data: + password: {{ .Values.password | b64enc | quote }} + username: {{ if .Values.username }}{{ .Values.username | b64enc | quote }}{{- else }}{{ .Values.name | b64enc | quote }}{{- end }} diff --git a/examples/helm/postgres/values.yaml b/examples/helm/postgres/values.yaml new file mode 100644 index 0000000000..60b4b3e53b --- /dev/null +++ b/examples/helm/postgres/values.yaml @@ -0,0 +1,14 @@ +# The values is for the namespace and the postgresql cluster name +name: hippo +namespace: pgo +password: W4tch0ut4hippo$ + +# Optional parameters +# cpu: 0.25 +# diskSize: 5Gi +# monitoring: true +# ha: true +# imagePrefix: registry.developers.crunchydata.com/crunchydata +# image: crunchy-postgres-ha +# imageTag: centos8-13.1-4.6.0-beta.2 +# memory: 1Gi From 52f759e5c05570f952cf1570c624739d76073870 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 11 Jan 2021 11:51:02 -0500 Subject: [PATCH 135/276] Embed pgMonitor assets into pgo-deployer container This allows for the pgo-deployer container to install the monitoring stack without making any additional calls to the Internet. If the installation script does not detect the presence of the asset files, it will attempt to download them from the Internet. Issue: [ch10107] Issue: #1987 --- build/pgo-deployer/Dockerfile | 2 ++ .../ansible/roles/pgo-metrics/tasks/alertmanager.yml | 2 +- .../ansible/roles/pgo-metrics/tasks/grafana.yml | 6 +++--- .../metrics/ansible/roles/pgo-metrics/tasks/main.yml | 11 +++++++++-- .../ansible/roles/pgo-metrics/tasks/prometheus.yml | 8 ++++---- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/build/pgo-deployer/Dockerfile b/build/pgo-deployer/Dockerfile index c2000eaa87..2f18411334 100644 --- a/build/pgo-deployer/Dockerfile +++ b/build/pgo-deployer/Dockerfile @@ -70,6 +70,7 @@ fi COPY installers/ansible /ansible/postgres-operator COPY installers/metrics/ansible /ansible/metrics +ADD tools/pgmonitor /tmp/.pgo/metrics/pgmonitor COPY installers/image/bin/pgo-deploy.sh /pgo-deploy.sh COPY bin/uid_daemon.sh /uid_daemon.sh @@ -78,6 +79,7 @@ ENV HOME="/tmp" RUN chmod g=u /etc/passwd RUN chmod g=u /uid_daemon.sh +RUN chown -R 2:2 /tmp/.pgo/metrics ENTRYPOINT ["/uid_daemon.sh"] diff --git a/installers/metrics/ansible/roles/pgo-metrics/tasks/alertmanager.yml b/installers/metrics/ansible/roles/pgo-metrics/tasks/alertmanager.yml index dc82e92d1a..13fd013290 100644 --- a/installers/metrics/ansible/roles/pgo-metrics/tasks/alertmanager.yml +++ b/installers/metrics/ansible/roles/pgo-metrics/tasks/alertmanager.yml @@ -16,7 +16,7 @@ - name: Set pgmonitor Prometheus Directory Fact set_fact: - pgmonitor_prometheus_dir: "{{ metrics_dir }}/pgmonitor-{{ pgmonitor_version | replace('v','') }}/prometheus" + pgmonitor_prometheus_dir: "{{ metrics_dir }}/pgmonitor/prometheus" - name: Copy Alertmanger Config to Output Directory command: "cp {{ pgmonitor_prometheus_dir }}/{{ item.src }} {{ alertmanager_output_dir }}/{{ item.dst }}" diff --git a/installers/metrics/ansible/roles/pgo-metrics/tasks/grafana.yml b/installers/metrics/ansible/roles/pgo-metrics/tasks/grafana.yml index 1d528429b5..f0b88e0c65 100644 --- a/installers/metrics/ansible/roles/pgo-metrics/tasks/grafana.yml +++ b/installers/metrics/ansible/roles/pgo-metrics/tasks/grafana.yml @@ -9,7 +9,7 @@ grafana_output_dir: "{{ metrics_dir }}/output/grafana" - name: Ensure Output Directory Exists - file: + file: path: "{{ grafana_output_dir }}" state: "directory" mode: "0700" @@ -48,7 +48,7 @@ - name: Set pgmonitor Grafana Directory Fact set_fact: - pgmonitor_grafana_dir: "{{ metrics_dir }}/pgmonitor-{{ pgmonitor_version | replace('v','') }}/grafana" + pgmonitor_grafana_dir: "{{ metrics_dir }}/pgmonitor/grafana" - name: Copy Grafana Config to Output Directory command: "cp {{ pgmonitor_grafana_dir }}/{{ item }} {{ grafana_output_dir }}" @@ -111,7 +111,7 @@ src: "{{ item }}" dest: "{{ grafana_output_dir }}/{{ item | replace('.j2', '') }}" mode: "0600" - loop: + loop: - grafana-pvc.json.j2 - grafana-service.json.j2 - grafana-deployment.json.j2 diff --git a/installers/metrics/ansible/roles/pgo-metrics/tasks/main.yml b/installers/metrics/ansible/roles/pgo-metrics/tasks/main.yml index 425d3f8e1b..ae2c27b8e3 100644 --- a/installers/metrics/ansible/roles/pgo-metrics/tasks/main.yml +++ b/installers/metrics/ansible/roles/pgo-metrics/tasks/main.yml @@ -1,7 +1,7 @@ --- - name: Set Metrics Directory Fact set_fact: - metrics_dir: "{{ ansible_env.HOME }}/.pgo/metrics/{{ metrics_namespace }}" + metrics_dir: "{{ ansible_env.HOME }}/.pgo/metrics" tags: always - name: Ensure Output Directory Exists @@ -54,16 +54,23 @@ - install-metrics - update-metrics block: + - name: Check for pgmonitor + stat: + path: "{{ metrics_dir }}/pgmonitor" + register: pgmonitor_dir + - name: Download pgmonitor {{ pgmonitor_version }} get_url: url: https://github.com/CrunchyData/pgmonitor/archive/{{ pgmonitor_version }}.tar.gz dest: "{{ metrics_dir }}" mode: "0600" + when: not pgmonitor_dir.stat.exists - name: Extract pgmonitor unarchive: src: "{{ metrics_dir }}/pgmonitor-{{ pgmonitor_version | replace('v','') }}.tar.gz" - dest: "{{ metrics_dir }}" + dest: "{{ metrics_dir }}/pgmonitor" + when: not pgmonitor_dir.stat.exists - name: Create Metrics Image Pull Secret shell: > diff --git a/installers/metrics/ansible/roles/pgo-metrics/tasks/prometheus.yml b/installers/metrics/ansible/roles/pgo-metrics/tasks/prometheus.yml index ffcfa7c625..b9d70aad1f 100644 --- a/installers/metrics/ansible/roles/pgo-metrics/tasks/prometheus.yml +++ b/installers/metrics/ansible/roles/pgo-metrics/tasks/prometheus.yml @@ -9,7 +9,7 @@ prom_output_dir: "{{ metrics_dir }}/output/prom" - name: Ensure Output Directory Exists - file: + file: path: "{{ prom_output_dir }}" state: "directory" mode: "0700" @@ -22,7 +22,7 @@ loop: - prometheus-rbac.json.j2 when: create_rbac | bool - + - name: Create Prometheus RBAC command: "{{ kubectl_or_oc }} create -f {{ prom_output_dir }}/{{ item }} -n {{ metrics_namespace }}" loop: @@ -35,7 +35,7 @@ - name: Set pgmonitor Prometheus Directory Fact set_fact: - pgmonitor_prometheus_dir: "{{ metrics_dir }}/pgmonitor-{{ pgmonitor_version | replace('v','') }}/prometheus" + pgmonitor_prometheus_dir: "{{ metrics_dir }}/pgmonitor/prometheus" - name: Copy Prometheus Config to Output Directory command: "cp {{ pgmonitor_prometheus_dir }}/{{ item.src }} {{ prom_output_dir }}/{{ item.dst }}" @@ -88,7 +88,7 @@ src: "{{ item }}" dest: "{{ prom_output_dir }}/{{ item | replace('.j2', '') }}" mode: "0600" - loop: + loop: - prometheus-pvc.json.j2 - prometheus-service.json.j2 - prometheus-deployment.json.j2 From 0c687e6b9c53de00ab37b36f05cf9878fc2a3cba Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 11 Jan 2021 16:39:40 -0500 Subject: [PATCH 136/276] Do not restrict max version of `pgo upgrade` This created maintenance burden. --- internal/apiserver/upgradeservice/upgradeimpl.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/internal/apiserver/upgradeservice/upgradeimpl.go b/internal/apiserver/upgradeservice/upgradeimpl.go index ee41b1194c..861cbbe655 100644 --- a/internal/apiserver/upgradeservice/upgradeimpl.go +++ b/internal/apiserver/upgradeservice/upgradeimpl.go @@ -36,7 +36,6 @@ import ( // Currently supported version information for upgrades const ( REQUIRED_MAJOR_PGO_VERSION = 4 - MAXIMUM_MINOR_PGO_VERSION = 5 MINIMUM_MINOR_PGO_VERSION = 1 ) @@ -224,12 +223,9 @@ func supportedOperatorVersion(version string) bool { log.Errorf("Cannot convert Postgres Operator's minor version to an integer. Error: %v", err) return false } - if minor < MINIMUM_MINOR_PGO_VERSION || minor > MAXIMUM_MINOR_PGO_VERSION { - return false - } // If none of the above is true, the upgrade can continue - return true + return minor >= MINIMUM_MINOR_PGO_VERSION } // upgradeTagValid compares and validates the PostgreSQL version values stored From 89f5b435113eabab0996a23cbfbe7081b97e2d7e Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 11 Jan 2021 16:40:24 -0500 Subject: [PATCH 137/276] Do not delete primary PVC during upgrade The new pgcluster deletion logic creates an opportunity to delete the primary and backup PVC, which we do not want to do during an upgrade. Instead, indicate on the pgcluster object that there is an upgrade in progress, which will cause the controller to ignore the change. --- internal/config/annotations.go | 3 ++ .../pgcluster/pgclustercontroller.go | 8 +++- internal/operator/cluster/upgrade.go | 40 ++++++++++++++----- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/internal/config/annotations.go b/internal/config/annotations.go index bde7a345f8..f8a0b32023 100644 --- a/internal/config/annotations.go +++ b/internal/config/annotations.go @@ -31,6 +31,9 @@ const ( ANNOTATION_CURRENT_PRIMARY = "current-primary" // annotation to indicate whether a cluster has been upgraded ANNOTATION_IS_UPGRADED = "is-upgraded" + // annotation to indicate an upgrade is in progress. this has the effect + // of causeing the rmdata job in pgcluster to not run + ANNOTATION_UPGRADE_IN_PROGRESS = "upgrade-in-progress" // annotation to store the Operator versions upgraded from and to ANNOTATION_UPGRADE_INFO = "upgrade-info" // annotation to store the string boolean, used when checking upgrade status diff --git a/internal/controller/pgcluster/pgclustercontroller.go b/internal/controller/pgcluster/pgclustercontroller.go index 576a5d0af9..ef23c14b87 100644 --- a/internal/controller/pgcluster/pgclustercontroller.go +++ b/internal/controller/pgcluster/pgclustercontroller.go @@ -350,7 +350,13 @@ func (c *Controller) onDelete(obj interface{}) { log.Debugf("pgcluster onDelete for cluster %s (namespace %s)", cluster.Name, cluster.Namespace) - // a quick guard: see if the "rmdata Job" is running. + // guard: if an upgrade is in progress, do not do any of the rest + if _, ok := cluster.ObjectMeta.GetAnnotations()[config.ANNOTATION_UPGRADE_IN_PROGRESS]; ok { + log.Debug("upgrade in progress, not proceeding with additional cleanups") + return + } + + // guard: see if the "rmdata Job" is running. options := metav1.ListOptions{ LabelSelector: fields.AndSelectors( fields.OneTermEqualSelector(config.LABEL_PG_CLUSTER, cluster.Name), diff --git a/internal/operator/cluster/upgrade.go b/internal/operator/cluster/upgrade.go index c55d405a24..93cec5e704 100644 --- a/internal/operator/cluster/upgrade.go +++ b/internal/operator/cluster/upgrade.go @@ -104,7 +104,11 @@ func AddUpgrade(clientset kubeapi.Interface, upgrade *crv1.Pgtask, namespace str _ = createUpgradePGHAConfigMap(clientset, pgcluster, namespace) // delete the existing pgcluster CRDs and other resources that will be recreated - deleteBeforeUpgrade(clientset, pgcluster.Name, currentPrimary, namespace) + if err := deleteBeforeUpgrade(clientset, pgcluster, currentPrimary, namespace); err != nil { + log.Error("refusing to upgrade due to unsuccessful resource removal") + PublishUpgradeEvent(events.EventUpgradeClusterFailure, namespace, upgrade, err.Error()) + return + } // recreate new Backrest Repo secret that was just deleted recreateBackrestRepoSecret(clientset, upgradeTargetClusterName, namespace, operator.PgoNamespace) @@ -257,13 +261,25 @@ func SetReplicaNumber(pgcluster *crv1.Pgcluster, numReplicas string) { // deleteBeforeUpgrade deletes the deployments, services, pgcluster, jobs, tasks and default configmaps before attempting // to upgrade the pgcluster deployment. This preserves existing secrets, non-standard configmaps and service definitions // for use in the newly upgraded cluster. -func deleteBeforeUpgrade(clientset kubeapi.Interface, clusterName, currentPrimary, namespace string) { +func deleteBeforeUpgrade(clientset kubeapi.Interface, pgcluster *crv1.Pgcluster, currentPrimary, namespace string) error { ctx := context.TODO() - // first, get all deployments for the pgcluster in question + // first, indicate that there is an upgrade occurring on this custom resource + // this will prevent the rmdata job from firing off + annotations := pgcluster.ObjectMeta.GetAnnotations() + annotations[config.ANNOTATION_UPGRADE_IN_PROGRESS] = config.LABEL_TRUE + pgcluster.ObjectMeta.SetAnnotations(annotations) + + if _, err := clientset.CrunchydataV1().Pgclusters(namespace).Update(ctx, + pgcluster, metav1.UpdateOptions{}); err != nil { + log.Errorf("unable to set annotations to keep backups and data: %s", err) + return err + } + + // next, get all deployments for the pgcluster in question deployments, err := clientset. AppsV1().Deployments(namespace). - List(ctx, metav1.ListOptions{LabelSelector: config.LABEL_PG_CLUSTER + "=" + clusterName}) + List(ctx, metav1.ListOptions{LabelSelector: config.LABEL_PG_CLUSTER + "=" + pgcluster.Name}) if err != nil { log.Errorf("unable to get deployments. Error: %s", err) } @@ -279,7 +295,7 @@ func deleteBeforeUpgrade(clientset kubeapi.Interface, clusterName, currentPrimar } // wait until the backrest shared repo pod deployment has been deleted before continuing - waitStatus := deploymentWait(clientset, namespace, clusterName+"-backrest-shared-repo", + waitStatus := deploymentWait(clientset, namespace, pgcluster.Name+"-backrest-shared-repo", 180*time.Second, 10*time.Second) log.Debug(waitStatus) // wait until the primary pod deployment has been deleted before continuing @@ -288,7 +304,7 @@ func deleteBeforeUpgrade(clientset kubeapi.Interface, clusterName, currentPrimar log.Debug(waitStatus) // delete the pgcluster - _ = clientset.CrunchydataV1().Pgclusters(namespace).Delete(ctx, clusterName, metav1.DeleteOptions{}) + _ = clientset.CrunchydataV1().Pgclusters(namespace).Delete(ctx, pgcluster.Name, metav1.DeleteOptions{}) // delete all existing job references deletePropagation := metav1.DeletePropagationForeground @@ -296,23 +312,25 @@ func deleteBeforeUpgrade(clientset kubeapi.Interface, clusterName, currentPrimar BatchV1().Jobs(namespace). DeleteCollection(ctx, metav1.DeleteOptions{PropagationPolicy: &deletePropagation}, - metav1.ListOptions{LabelSelector: config.LABEL_PG_CLUSTER + "=" + clusterName}) + metav1.ListOptions{LabelSelector: config.LABEL_PG_CLUSTER + "=" + pgcluster.Name}) // delete all existing pgtask references except for the upgrade task // Note: this will be deleted by the existing pgcluster creation process once the // updated pgcluster created and processed by the cluster controller - if err = deleteNonupgradePgtasks(clientset, config.LABEL_PG_CLUSTER+"="+clusterName, namespace); err != nil { - log.Errorf("error while deleting pgtasks for cluster %s, Error: %v", clusterName, err) + if err = deleteNonupgradePgtasks(clientset, config.LABEL_PG_CLUSTER+"="+pgcluster.Name, namespace); err != nil { + log.Errorf("error while deleting pgtasks for cluster %s, Error: %v", pgcluster.Name, err) } // delete the leader configmap used by the Postgres Operator since this information may change after // the upgrade is complete // Note: deletion is required for cluster recreation - _ = clientset.CoreV1().ConfigMaps(namespace).Delete(ctx, clusterName+"-leader", metav1.DeleteOptions{}) + _ = clientset.CoreV1().ConfigMaps(namespace).Delete(ctx, pgcluster.Name+"-leader", metav1.DeleteOptions{}) // delete the '-pgha-default-config' configmap, if it exists so the config syncer // will not try to use it instead of '-pgha-config' - _ = clientset.CoreV1().ConfigMaps(namespace).Delete(ctx, clusterName+"-pgha-default-config", metav1.DeleteOptions{}) + _ = clientset.CoreV1().ConfigMaps(namespace).Delete(ctx, pgcluster.Name+"-pgha-default-config", metav1.DeleteOptions{}) + + return nil } // deploymentWait is modified from cluster.waitForDeploymentDelete. It simply waits for the current primary deployment From 72df1a1fd764059fb0c0da7bae0913bfefc194ed Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 11 Jan 2021 17:51:20 -0500 Subject: [PATCH 138/276] Ensure PostgreSQL cluster comes back up during upgrade This explicitly ensures that the PGHA_INIT flag is only set by the Operator if it has not been explicitly set prior. Co-authored-by: Andrew L'Ecuyer --- internal/operator/cluster/cluster.go | 42 ++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/internal/operator/cluster/cluster.go b/internal/operator/cluster/cluster.go index 1a96e004a1..d07938e8e7 100644 --- a/internal/operator/cluster/cluster.go +++ b/internal/operator/cluster/cluster.go @@ -99,13 +99,20 @@ func AddClusterBase(clientset kubeapi.Interface, cl *crv1.Pgcluster, namespace s // logic following a restart of the container. // If the configmap already exists, the cluster creation will continue as this is required // for certain pgcluster upgrades. - if err = operator.CreatePGHAConfigMap(clientset, cl, + if err := operator.CreatePGHAConfigMap(clientset, cl, namespace); kerrors.IsAlreadyExists(err) { - log.Infof("found existing pgha ConfigMap for cluster %s, setting init flag to 'true'", - cl.GetName()) - err = operator.UpdatePGHAConfigInitFlag(clientset, true, cl.Name, cl.Namespace) - } - if err != nil { + if !pghaConigMapHasInitFlag(clientset, cl) { + log.Infof("found existing pgha ConfigMap for cluster %s without init flag set. "+ + "setting init flag to 'true'", cl.GetName()) + + // if the value is not present, update the config map + if err := operator.UpdatePGHAConfigInitFlag(clientset, true, cl.Name, cl.Namespace); err != nil { + log.Error(err) + publishClusterCreateFailure(cl, err.Error()) + return + } + } + } else if err != nil { log.Error(err) publishClusterCreateFailure(cl, err.Error()) return @@ -728,6 +735,29 @@ func createMissingUserSecrets(clientset kubernetes.Interface, cluster *crv1.Pgcl return createMissingUserSecret(clientset, cluster, cluster.Spec.User) } +// pghaConigMapHasInitFlag checks to see if the PostgreSQL ConfigMap has the +// PGHA init flag. Returns true if it does have it set, false otherwise. +// If any function calls have an error, we will log that error and return false +func pghaConigMapHasInitFlag(clientset kubernetes.Interface, cluster *crv1.Pgcluster) bool { + ctx := context.TODO() + + // load the PGHA config map for this cluster. This more or less assumes that + // it exists + configMapName := fmt.Sprintf("%s-%s", cluster.Name, operator.PGHAConfigMapSuffix) + configMap, err := clientset.CoreV1().ConfigMaps(cluster.Namespace).Get(ctx, configMapName, metav1.GetOptions{}) + + // if there is an error getting the ConfigMap, log the error and return + if err != nil { + log.Error(err) + return false + } + + // determine if the init flag is set, regardless of it's true or false + _, ok := configMap.Data[operator.PGHAConfigInitSetting] + + return ok +} + func publishClusterCreateFailure(cl *crv1.Pgcluster, errorMsg string) { pgouser := cl.ObjectMeta.Labels[config.LABEL_PGOUSER] topics := make([]string, 1) From 64197f179be7429cff672493cd5389cb27531df6 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Wed, 13 Jan 2021 12:43:46 -0500 Subject: [PATCH 139/276] Gracefully handle names with "replica" in `pgo test` This provides an even tighter check than the one introduced in b0a276ab1 to determine what is a primary vs. replica Service. Issue: [ch9764] Issue: #2047 --- internal/apiserver/clusterservice/clusterimpl.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index bb42a5dd40..3dfad2b092 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -483,7 +483,7 @@ func TestCluster(name, selector, ns, pgouser string, allFlag bool) msgs.ClusterT switch { default: endpoint.InstanceType = msgs.ClusterTestInstanceTypePrimary - case strings.HasSuffix(service.Name, msgs.PodTypeReplica): + case (strings.HasSuffix(service.Name, "-"+msgs.PodTypeReplica) && strings.Count(service.Name, "-"+msgs.PodTypeReplica) == 1): endpoint.InstanceType = msgs.ClusterTestInstanceTypeReplica case service.Pgbouncer: endpoint.InstanceType = msgs.ClusterTestInstanceTypePGBouncer From f37214c63d373991214d396750814dbb564b5bb1 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Wed, 13 Jan 2021 14:25:57 -0500 Subject: [PATCH 140/276] Make "Effect" optional when setting a toleration via `pgo` client Per Kubernetes documentation, one does not need to set an Effect when setting a Toleration, so our CLI should allow for this to be optional. Issue: [ch10147] --- cmd/pgo/cmd/cluster.go | 30 +++++++++++++++++----- cmd/pgo/cmd/create.go | 2 ++ docs/content/tutorial/customize-cluster.md | 6 +++++ 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/cmd/pgo/cmd/cluster.go b/cmd/pgo/cmd/cluster.go index e66a28fc32..848fd0e995 100644 --- a/cmd/pgo/cmd/cluster.go +++ b/cmd/pgo/cmd/cluster.go @@ -555,8 +555,13 @@ func getTablespaces(tablespaceParams []string) []msgs.ClusterTablespaceDetail { // // Operator - rule:Effect // -// Exists - key:Effect -// Equals - key=value:Effect +// Exists: +// - key +// - key:Effect +// +// Equals: +// - key=value +// - key=value:Effect // // If the remove flag is set to true, check for a trailing "-" at the end of // each item, as this will be a remove list. Otherwise, only consider @@ -575,22 +580,32 @@ func getClusterTolerations(tolerationList []string, remove bool) []v1.Toleration ruleEffect := strings.Split(t, ":") // if we don't have exactly two items, then error - if len(ruleEffect) != 2 { + if len(ruleEffect) < 1 || len(ruleEffect) > 2 { fmt.Printf("invalid format for toleration: %q\n", t) os.Exit(1) } // for ease of reading - rule, effectStr := ruleEffect[0], ruleEffect[1] + rule, effectStr := ruleEffect[0], "" + // effect string is only set if ruleEffect is of length 2 + if len(ruleEffect) == 2 { + effectStr = ruleEffect[1] + } // determine if the effect is for removal or not, as we will continue the - // loop based on that - if (remove && !strings.HasSuffix(effectStr, "-")) || (!remove && strings.HasSuffix(effectStr, "-")) { + // loop based on that. + // + // In other words, skip processing the value if either: + // - This *is* removal mode AND the value *does not* have the removal suffix "-" + // - This *is not* removal mode AND the value *does* have the removal suffix "-" + if (remove && !strings.HasSuffix(effectStr, "-") && !strings.HasSuffix(rule, "-")) || + (!remove && (strings.HasSuffix(effectStr, "-") || strings.HasSuffix(rule, "-"))) { continue } // no matter what we can trim any trailing "-" off of the string, and cast // it as a TaintEffect + rule = strings.TrimSuffix(rule, "-") effect := v1.TaintEffect(strings.TrimSuffix(effectStr, "-")) // see if the effect is a valid effect @@ -633,7 +648,8 @@ func getClusterTolerations(tolerationList []string, remove bool) []v1.Toleration func isValidTaintEffect(taintEffect v1.TaintEffect) bool { return (taintEffect == v1.TaintEffectNoSchedule || taintEffect == v1.TaintEffectPreferNoSchedule || - taintEffect == v1.TaintEffectNoExecute) + taintEffect == v1.TaintEffectNoExecute || + taintEffect == "") } // isTablespaceParam returns true if the parameter in question is acceptable for diff --git a/cmd/pgo/cmd/create.go b/cmd/pgo/cmd/create.go index fa6e71e32b..d1cd3eaa14 100644 --- a/cmd/pgo/cmd/create.go +++ b/cmd/pgo/cmd/create.go @@ -153,6 +153,8 @@ var ( // Exists - key:Effect // Equals - key=value:Effect // +// Effect can be optional. +// // Example: // // zone=east:NoSchedule,highspeed:NoSchedule diff --git a/docs/content/tutorial/customize-cluster.md b/docs/content/tutorial/customize-cluster.md index 8d5f4f941d..7006b70a20 100644 --- a/docs/content/tutorial/customize-cluster.md +++ b/docs/content/tutorial/customize-cluster.md @@ -146,6 +146,12 @@ The PostgreSQL Operator supports adding tolerations to PostgreSQL instances usin rule:Effect ``` +or + +``` +rule +``` + where a `rule` can represent existence (e.g. `key`) or equality (`key=value`) and `Effect` is one of `NoSchedule`, `PreferNoSchedule`, or `NoExecute`. For more information on how tolerations work, please refer to the [Kubernetes documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/). You can assign multiple tolerations to a PostgreSQL cluster. From b4059a387d5dd6de206657e214273a8de16c6f4b Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Wed, 13 Jan 2021 14:45:28 -0500 Subject: [PATCH 141/276] Remove dead code around cluster deletion This was not being referenced anywhere and caused confusion, especially in reference to similar funcitonality that exists elsewhere. --- internal/operator/cluster/clusterlogic.go | 43 ----------- internal/operator/cluster/rmdata.go | 92 ----------------------- 2 files changed, 135 deletions(-) delete mode 100644 internal/operator/cluster/rmdata.go diff --git a/internal/operator/cluster/clusterlogic.go b/internal/operator/cluster/clusterlogic.go index 90726ec172..cb8b9e3e38 100644 --- a/internal/operator/cluster/clusterlogic.go +++ b/internal/operator/cluster/clusterlogic.go @@ -353,28 +353,6 @@ func getClusterDeploymentFields(clientset kubernetes.Interface, return deploymentFields } -// DeleteCluster ... -func DeleteCluster(clientset kubernetes.Interface, cl *crv1.Pgcluster, namespace string) error { - var err error - log.Info("deleting Pgcluster object" + " in namespace " + namespace) - log.Info("deleting with Name=" + cl.Spec.Name + " in namespace " + namespace) - - // create rmdata job - isReplica := false - isBackup := false - removeData := true - removeBackup := false - err = CreateRmdataJob(clientset, cl, namespace, removeData, removeBackup, isReplica, isBackup) - if err != nil { - log.Error(err) - return err - } else { - publishDeleteCluster(namespace, cl.ObjectMeta.Labels[config.LABEL_PGOUSER], cl.Spec.Name) - } - - return err -} - // scaleReplicaCreateMissingService creates a service for cluster replicas if // it does not yet exist. func scaleReplicaCreateMissingService(clientset kubernetes.Interface, replica *crv1.Pgreplica, cluster *crv1.Pgcluster, namespace string) error { @@ -600,27 +578,6 @@ func publishScaleError(namespace string, username string, cluster *crv1.Pgcluste } } -func publishDeleteCluster(namespace, username, clusterName string) { - topics := make([]string, 1) - topics[0] = events.EventTopicCluster - - f := events.EventDeleteClusterFormat{ - EventHeader: events.EventHeader{ - Namespace: namespace, - Username: username, - Topic: topics, - Timestamp: time.Now(), - EventType: events.EventDeleteCluster, - }, - Clustername: clusterName, - } - - err := events.Publish(f) - if err != nil { - log.Error(err.Error()) - } -} - // ScaleClusterInfo contains information about a cluster obtained when scaling the various // deployments for a cluster. This includes the name of the primary deployment, all replica // deployments, along with the names of the services enabled for the cluster. diff --git a/internal/operator/cluster/rmdata.go b/internal/operator/cluster/rmdata.go deleted file mode 100644 index 6aa4e986a0..0000000000 --- a/internal/operator/cluster/rmdata.go +++ /dev/null @@ -1,92 +0,0 @@ -// Package cluster holds the cluster CRD logic and definitions -// A cluster is comprised of a primary service, replica service, -// primary deployment, and replica deployment -package cluster - -/* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. - 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. -*/ - -import ( - "bytes" - "context" - "encoding/json" - "os" - "strconv" - - "github.com/crunchydata/postgres-operator/internal/config" - "github.com/crunchydata/postgres-operator/internal/operator" - "github.com/crunchydata/postgres-operator/internal/util" - crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" - log "github.com/sirupsen/logrus" - v1batch "k8s.io/api/batch/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" -) - -type RmdataJob struct { - JobName string - ClusterName string - PGOImagePrefix string - PGOImageTag string - // SecurityContext string - RemoveData string - RemoveBackup string - IsBackup string - IsReplica string -} - -func CreateRmdataJob(clientset kubernetes.Interface, cl *crv1.Pgcluster, namespace string, removeData, removeBackup, isReplica, isBackup bool) error { - ctx := context.TODO() - var err error - - jobName := cl.Spec.Name + "-rmdata-" + util.RandStringBytesRmndr(4) - - jobFields := RmdataJob{ - JobName: jobName, - ClusterName: cl.Spec.Name, - PGOImagePrefix: util.GetValueOrDefault(cl.Spec.PGOImagePrefix, operator.Pgo.Pgo.PGOImagePrefix), - PGOImageTag: operator.Pgo.Pgo.PGOImageTag, - RemoveData: strconv.FormatBool(removeData), - RemoveBackup: strconv.FormatBool(removeBackup), - IsBackup: strconv.FormatBool(isReplica), - IsReplica: strconv.FormatBool(isBackup), - } - - doc := bytes.Buffer{} - - if err := config.RmdatajobTemplate.Execute(&doc, jobFields); err != nil { - log.Error(err.Error()) - return err - } - - if operator.CRUNCHY_DEBUG { - _ = config.RmdatajobTemplate.Execute(os.Stdout, jobFields) - } - - newjob := v1batch.Job{} - - if err := json.Unmarshal(doc.Bytes(), &newjob); err != nil { - log.Error("error unmarshalling json into Job " + err.Error()) - return err - } - - // set the container image to an override value, if one exists - operator.SetContainerImageOverride(config.CONTAINER_IMAGE_PGO_RMDATA, - &newjob.Spec.Template.Spec.Containers[0]) - - _, err = clientset.BatchV1().Jobs(namespace). - Create(ctx, &newjob, metav1.CreateOptions{}) - return err -} From 3c5143084e7e9df3c597e00f036c62a39d59b48a Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Wed, 13 Jan 2021 15:11:44 -0500 Subject: [PATCH 142/276] Have pgBackRest and associated jobs respect cluster tolerations pgBackRest is a close corollary to the PostgreSQL cluster itself and should respect similar tolerations to the PostgreSQL cluster. Future work could break out tolerations specific for pgBackRest, but given the two do need to work in concert, it is prudent to start with this approach. Issue: [ch10146] --- cmd/pgo-scheduler/scheduler/policy.go | 1 + cmd/pgo-scheduler/scheduler/types.go | 1 + .../files/pgo-configs/backrest-job.json | 3 +++ .../pgo-configs/cluster-bootstrap-job.json | 3 +++ .../files/pgo-configs/pgdump-job.json | 3 +++ .../pgo-backrest-repo-template.json | 3 +++ .../pgo-configs/pgo.sqlrunner-template.json | 3 +++ .../files/pgo-configs/pgrestore-job.json | 3 +++ .../files/pgo-configs/rmdata-job.json | 3 +++ internal/config/labels.go | 1 + internal/operator/backrest/backup.go | 2 ++ internal/operator/backrest/repo.go | 2 ++ internal/operator/cluster/clusterlogic.go | 6 +++--- internal/operator/clusterutilities.go | 19 ----------------- internal/operator/pgdump/dump.go | 2 ++ internal/operator/pgdump/restore.go | 2 ++ internal/operator/task/rmdata.go | 2 ++ internal/util/cluster.go | 21 +++++++++++++++++++ 18 files changed, 58 insertions(+), 22 deletions(-) diff --git a/cmd/pgo-scheduler/scheduler/policy.go b/cmd/pgo-scheduler/scheduler/policy.go index c143df1978..00d92b9225 100644 --- a/cmd/pgo-scheduler/scheduler/policy.go +++ b/cmd/pgo-scheduler/scheduler/policy.go @@ -142,6 +142,7 @@ func (p PolicyJob) Run() { PGDatabase: p.database, PGSQLConfigMap: name, PGUserSecret: p.secret, + Tolerations: util.GetTolerations(cluster.Spec.Tolerations), } var doc bytes.Buffer diff --git a/cmd/pgo-scheduler/scheduler/types.go b/cmd/pgo-scheduler/scheduler/types.go index 7c766fa539..75a5b297b3 100644 --- a/cmd/pgo-scheduler/scheduler/types.go +++ b/cmd/pgo-scheduler/scheduler/types.go @@ -70,4 +70,5 @@ type PolicyTemplate struct { PGDatabase string PGUserSecret string PGSQLConfigMap string + Tolerations string } diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json index bf89971aa6..b2512650f5 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json @@ -29,6 +29,9 @@ ], "securityContext": {{.SecurityContext}}, "serviceAccountName": "pgo-backrest", + {{ if .Tolerations }} + "tolerations": {{ .Tolerations }}, + {{ end }} "containers": [{ "name": "backrest", "image": "{{.CCPImagePrefix}}/crunchy-pgbackrest:{{.CCPImageTag}}", diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json index ee5e5307a9..42ee6fe2b3 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json @@ -23,6 +23,9 @@ "spec": { "securityContext": {{.SecurityContext}}, "serviceAccountName": "pgo-pg", + {{ if .Tolerations }} + "tolerations": {{ .Tolerations }}, + {{ end }} "containers": [{ "name": "database", "image": "{{.CCPImagePrefix}}/{{.CCPImage}}:{{.CCPImageTag}}", diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgdump-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgdump-job.json index ef6e1b6d5a..9c44c0ce06 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgdump-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgdump-job.json @@ -31,6 +31,9 @@ ], "securityContext": {{.SecurityContext}}, "serviceAccountName": "pgo-default", + {{ if .Tolerations }} + "tolerations": {{ .Tolerations }}, + {{ end }} "containers": [{ "name": "pgdump", "image": "{{.CCPImagePrefix}}/crunchy-postgres-ha:{{.CCPImageTag}}", diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json index 885396322b..32a67d9a99 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json @@ -46,6 +46,9 @@ "spec": { "securityContext": {{.SecurityContext}}, "serviceAccountName": "pgo-default", + {{ if .Tolerations }} + "tolerations": {{ .Tolerations }}, + {{ end }} "containers": [{ "name": "database", "image": "{{.CCPImagePrefix}}/crunchy-pgbackrest-repo:{{.CCPImageTag}}", diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json index 56f55dd35e..41e1bfc552 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json @@ -21,6 +21,9 @@ }, "spec": { "serviceAccountName": "pgo-default", + {{ if .Tolerations }} + "tolerations": {{ .Tolerations }}, + {{ end }} "containers": [ { "name": "sqlrunner", diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json index 3759905e95..abcd5c2ee3 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json @@ -31,6 +31,9 @@ ], "securityContext": {{.SecurityContext}}, "serviceAccountName": "pgo-default", + {{ if .Tolerations }} + "tolerations": {{ .Tolerations }}, + {{ end }} "containers": [ { "name": "pgrestore", diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/rmdata-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/rmdata-job.json index b5f169fa4a..1b593e181e 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/rmdata-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/rmdata-job.json @@ -21,6 +21,9 @@ }, "spec": { "serviceAccountName": "pgo-target", + {{ if .Tolerations }} + "tolerations": {{ .Tolerations }}, + {{ end }} "containers": [{ "name": "rmdata", "image": "{{.PGOImagePrefix}}/pgo-rmdata:{{.PGOImageTag}}", diff --git a/internal/config/labels.go b/internal/config/labels.go index d07fdd3219..f7c55b79ea 100644 --- a/internal/config/labels.go +++ b/internal/config/labels.go @@ -60,6 +60,7 @@ const ( LABEL_DELETE_BACKUPS = "delete-backups" LABEL_IS_REPLICA = "is-replica" LABEL_IS_BACKUP = "is-backup" + LABEL_RM_TOLERATIONS = "rmdata-tolerations" LABEL_STARTUP = "startup" LABEL_SHUTDOWN = "shutdown" ) diff --git a/internal/operator/backrest/backup.go b/internal/operator/backrest/backup.go index 4d129b60ac..87cacf49f9 100644 --- a/internal/operator/backrest/backup.go +++ b/internal/operator/backrest/backup.go @@ -61,6 +61,7 @@ type backrestJobTemplateFields struct { PgbackrestS3VerifyTLS string PgbackrestRestoreVolumes string PgbackrestRestoreVolumeMounts string + Tolerations string } var ( @@ -117,6 +118,7 @@ func Backrest(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask) PgbackrestRepo1Type: repoType, BackrestLocalAndS3Storage: operator.IsLocalAndS3Storage(cluster), PgbackrestS3VerifyTLS: task.Spec.Parameters[config.LABEL_BACKREST_S3_VERIFY_TLS], + Tolerations: util.GetTolerations(cluster.Spec.Tolerations), } podCommandOpts, err := getCommandOptsFromPod(clientset, task, namespace) diff --git a/internal/operator/backrest/repo.go b/internal/operator/backrest/repo.go index 86304055d1..f06019a633 100644 --- a/internal/operator/backrest/repo.go +++ b/internal/operator/backrest/repo.go @@ -65,6 +65,7 @@ type RepoDeploymentTemplateFields struct { PodAntiAffinityLabelValue string Replicas int BootstrapCluster string + Tolerations string } type RepoServiceTemplateFields struct { @@ -250,6 +251,7 @@ func getRepoDeploymentFields(clientset kubernetes.Interface, cluster *crv1.Pgclu PodAntiAffinityLabelName: config.LABEL_POD_ANTI_AFFINITY, PodAntiAffinityLabelValue: string(operator.GetPodAntiAffinityType(cluster, crv1.PodAntiAffinityDeploymentPgBackRest, cluster.Spec.PodAntiAffinity.PgBackRest)), + Tolerations: util.GetTolerations(cluster.Spec.Tolerations), } return &repoFields diff --git a/internal/operator/cluster/clusterlogic.go b/internal/operator/cluster/clusterlogic.go index cb8b9e3e38..9169d1bedb 100644 --- a/internal/operator/cluster/clusterlogic.go +++ b/internal/operator/cluster/clusterlogic.go @@ -347,7 +347,7 @@ func getClusterDeploymentFields(clientset kubernetes.Interface, ReplicationTLSSecret: cl.Spec.TLS.ReplicationTLSSecret, CASecret: cl.Spec.TLS.CASecret, Standby: cl.Spec.Standby, - Tolerations: operator.GetTolerations(cl.Spec.Tolerations), + Tolerations: util.GetTolerations(cl.Spec.Tolerations), } return deploymentFields @@ -491,8 +491,8 @@ func scaleReplicaCreateDeployment(clientset kubernetes.Interface, // Give precedence to the tolerations defined on the replica spec, otherwise // take any tolerations defined on the cluster spec Tolerations: util.GetValueOrDefault( - operator.GetTolerations(replica.Spec.Tolerations), - operator.GetTolerations(cluster.Spec.Tolerations)), + util.GetTolerations(replica.Spec.Tolerations), + util.GetTolerations(cluster.Spec.Tolerations)), } switch replica.Spec.ReplicaStorage.StorageType { diff --git a/internal/operator/clusterutilities.go b/internal/operator/clusterutilities.go index 94744111ca..239bcbe8f3 100644 --- a/internal/operator/clusterutilities.go +++ b/internal/operator/clusterutilities.go @@ -955,25 +955,6 @@ func GetSyncReplication(specSyncReplication *bool) bool { return Pgo.Cluster.SyncReplication } -// GetTolerations returns any tolerations that may be defined in a tolerations -// in JSON format. Otherwise, it returns an empty string -func GetTolerations(tolerations []v1.Toleration) string { - // if no tolerations, exit early - if len(tolerations) == 0 { - return "" - } - - // turn into a JSON string - s, err := json.MarshalIndent(tolerations, "", " ") - - if err != nil { - log.Errorf("%s: returning empty string", err.Error()) - return "" - } - - return string(s) -} - // OverrideClusterContainerImages is a helper function that provides the // appropriate hooks to override any of the container images that might be // deployed with a PostgreSQL cluster diff --git a/internal/operator/pgdump/dump.go b/internal/operator/pgdump/dump.go index 73bf7cc0a1..0808bedf4f 100644 --- a/internal/operator/pgdump/dump.go +++ b/internal/operator/pgdump/dump.go @@ -53,6 +53,7 @@ type pgDumpJobTemplateFields struct { PgDumpFilename string PgDumpAll string PgDumpPVC string + Tolerations string } // Dump ... @@ -118,6 +119,7 @@ func Dump(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask) { PgDumpOpts: task.Spec.Parameters[config.LABEL_PGDUMP_OPTS], PgDumpAll: task.Spec.Parameters[config.LABEL_PGDUMP_ALL], PgDumpPVC: pvcName, + Tolerations: util.GetTolerations(cluster.Spec.Tolerations), } var doc2 bytes.Buffer diff --git a/internal/operator/pgdump/restore.go b/internal/operator/pgdump/restore.go index 51f36d0f0e..10e6567cb4 100644 --- a/internal/operator/pgdump/restore.go +++ b/internal/operator/pgdump/restore.go @@ -51,6 +51,7 @@ type restorejobTemplateFields struct { CCPImageTag string PgPort string NodeSelector string + Tolerations string } // Restore ... @@ -106,6 +107,7 @@ func Restore(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask) { CCPImagePrefix: util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, operator.Pgo.Cluster.CCPImagePrefix), CCPImageTag: operator.Pgo.Cluster.CCPImageTag, NodeSelector: operator.GetNodeAffinity(nodeAffinity), + Tolerations: util.GetTolerations(cluster.Spec.Tolerations), } var doc2 bytes.Buffer diff --git a/internal/operator/task/rmdata.go b/internal/operator/task/rmdata.go index 7f855ecf2e..5341a66156 100644 --- a/internal/operator/task/rmdata.go +++ b/internal/operator/task/rmdata.go @@ -47,6 +47,7 @@ type rmdatajobTemplateFields struct { RemoveBackup string IsBackup string IsReplica string + Tolerations string } // RemoveData ... @@ -98,6 +99,7 @@ func RemoveData(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask PGOImagePrefix: util.GetValueOrDefault(task.Spec.Parameters[config.LABEL_IMAGE_PREFIX], operator.Pgo.Pgo.PGOImagePrefix), PGOImageTag: operator.Pgo.Pgo.PGOImageTag, SecurityContext: operator.GetPodSecurityContext(task.Spec.StorageSpec.GetSupplementalGroups()), + Tolerations: task.Spec.Parameters[config.LABEL_RM_TOLERATIONS], } log.Debugf("creating rmdata job %s for cluster %s ", jobName, task.Spec.Name) diff --git a/internal/util/cluster.go b/internal/util/cluster.go index 364ce77993..2349cedbb0 100644 --- a/internal/util/cluster.go +++ b/internal/util/cluster.go @@ -17,6 +17,7 @@ package util import ( "context" + "encoding/json" "errors" "fmt" "strconv" @@ -262,6 +263,7 @@ func CreateRMDataTask(clientset kubeapi.Interface, cluster *crv1.Pgcluster, repl config.LABEL_PG_CLUSTER: cluster.Name, config.LABEL_REPLICA_NAME: replicaName, config.LABEL_PGHA_SCOPE: cluster.ObjectMeta.GetLabels()[config.LABEL_PGHA_SCOPE], + config.LABEL_RM_TOLERATIONS: GetTolerations(cluster.Spec.Tolerations), }, TaskType: crv1.PgtaskDeleteData, }, @@ -379,6 +381,25 @@ func GetS3CredsFromBackrestRepoSecret(clientset kubernetes.Interface, namespace, return s3Secret, nil } +// GetTolerations returns any tolerations that may be defined in a tolerations +// in JSON format. Otherwise, it returns an empty string +func GetTolerations(tolerations []v1.Toleration) string { + // if no tolerations, exit early + if len(tolerations) == 0 { + return "" + } + + // turn into a JSON string + s, err := json.MarshalIndent(tolerations, "", " ") + + if err != nil { + log.Errorf("%s: returning empty string", err.Error()) + return "" + } + + return string(s) +} + // SetPostgreSQLPassword updates the password for a PostgreSQL role in the // PostgreSQL cluster by executing into the primary Pod and changing it // From a6beb661652fb8a4f16225248ac022dbf45de15d Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Wed, 13 Jan 2021 16:47:44 -0500 Subject: [PATCH 143/276] Ensure standby cluster creates pgBouncer Secret The code was not allowing for this to happen. Even though the pgBouncer credential will need to be rotated after a standby is promoted, we can still create the credential with a nonworking default. Issue: [ch10083] --- internal/operator/cluster/pgbouncer.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/operator/cluster/pgbouncer.go b/internal/operator/cluster/pgbouncer.go index 35b788e9d8..1cb6b72716 100644 --- a/internal/operator/cluster/pgbouncer.go +++ b/internal/operator/cluster/pgbouncer.go @@ -164,6 +164,12 @@ func AddPgbouncer(clientset kubernetes.Interface, restconfig *rest.Config, clust if err := setPostgreSQLPassword(clientset, restconfig, pod, cluster.Spec.Port, crv1.PGUserPgBouncer, pgBouncerPassword); err != nil { return err } + } else { + // if this is a standby cluster, we still need to create a pgBouncer Secret, + // but no credentials are available + if err := createPgbouncerSecret(clientset, cluster, ""); err != nil { + return err + } } // next, create the pgBouncer config map that will allow pgBouncer to be From aa3e16937b25364d342ba496a19db993118eb668 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Wed, 13 Jan 2021 17:38:23 -0500 Subject: [PATCH 144/276] Remove restrictions on generic linking options for pgBackRest This is useful for creating a new cluster with an external WAL volume from a cluster that lacks one. Issue: [ch10157] --- internal/apiserver/backupoptions/pgbackrestoptions.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/apiserver/backupoptions/pgbackrestoptions.go b/internal/apiserver/backupoptions/pgbackrestoptions.go index ec18545fef..de5cbcb1b3 100644 --- a/internal/apiserver/backupoptions/pgbackrestoptions.go +++ b/internal/apiserver/backupoptions/pgbackrestoptions.go @@ -25,8 +25,6 @@ var pgBackRestOptsDenyList = []string{ "--config", "--config-include-path", "--config-path", - "--link-all", - "--link-map", "--lock-path", "--log-timestamp", "--neutral-umask", From 0eeafe7214dd4d53c042ac95614bb2372f76a755 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Wed, 13 Jan 2021 18:24:57 -0500 Subject: [PATCH 145/276] Reconcile API server permissions list Of note is the "Restart" permission which was not added to the validation list, and removing some permissions for calls that are no longer available. Issue: #2203 Issue: #2201 --- docs/content/Security/configure-postgres-operator-rbac.md | 1 + internal/apiserver/perms.go | 7 +------ 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/content/Security/configure-postgres-operator-rbac.md b/docs/content/Security/configure-postgres-operator-rbac.md index 63c9e7d36e..4a6d9e5da2 100644 --- a/docs/content/Security/configure-postgres-operator-rbac.md +++ b/docs/content/Security/configure-postgres-operator-rbac.md @@ -72,6 +72,7 @@ The following list shows the current complete list of possible pgo permissions t |DfCluster | allow *pgo df*| |Label | allow *pgo label*| |Reload | allow *pgo reload*| +|Restart | allow *pgo restart*| |Restore | allow *pgo restore*| |RestoreDump | allow *pgo restore* for pgdumps| |ShowBackup | allow *pgo show backup*| diff --git a/internal/apiserver/perms.go b/internal/apiserver/perms.go index e4e95978ae..01906db43e 100644 --- a/internal/apiserver/perms.go +++ b/internal/apiserver/perms.go @@ -40,7 +40,6 @@ const ( CREATE_CLUSTER_PERM = "CreateCluster" CREATE_DUMP_PERM = "CreateDump" CREATE_FAILOVER_PERM = "CreateFailover" - CREATE_INGEST_PERM = "CreateIngest" CREATE_NAMESPACE_PERM = "CreateNamespace" CREATE_PGADMIN_PERM = "CreatePgAdmin" CREATE_PGBOUNCER_PERM = "CreatePgbouncer" @@ -57,7 +56,6 @@ const ( // DELETE DELETE_BACKUP_PERM = "DeleteBackup" DELETE_CLUSTER_PERM = "DeleteCluster" - DELETE_INGEST_PERM = "DeleteIngest" DELETE_NAMESPACE_PERM = "DeleteNamespace" DELETE_PGADMIN_PERM = "DeletePgAdmin" DELETE_PGBOUNCER_PERM = "DeletePgbouncer" @@ -71,7 +69,6 @@ const ( SHOW_BACKUP_PERM = "ShowBackup" SHOW_CLUSTER_PERM = "ShowCluster" SHOW_CONFIG_PERM = "ShowConfig" - SHOW_INGEST_PERM = "ShowIngest" SHOW_NAMESPACE_PERM = "ShowNamespace" SHOW_PGADMIN_PERM = "ShowPgAdmin" SHOW_PGBOUNCER_PERM = "ShowPgBouncer" @@ -114,6 +111,7 @@ func initializePerms() { DF_CLUSTER_PERM: "yes", LABEL_PERM: "yes", RELOAD_PERM: "yes", + RESTART_PERM: "yes", RESTORE_PERM: "yes", STATUS_PERM: "yes", TEST_CLUSTER_PERM: "yes", @@ -124,7 +122,6 @@ func initializePerms() { CREATE_DUMP_PERM: "yes", CREATE_CLUSTER_PERM: "yes", CREATE_FAILOVER_PERM: "yes", - CREATE_INGEST_PERM: "yes", CREATE_NAMESPACE_PERM: "yes", CREATE_PGADMIN_PERM: "yes", CREATE_PGBOUNCER_PERM: "yes", @@ -141,7 +138,6 @@ func initializePerms() { // DELETE DELETE_BACKUP_PERM: "yes", DELETE_CLUSTER_PERM: "yes", - DELETE_INGEST_PERM: "yes", DELETE_NAMESPACE_PERM: "yes", DELETE_PGADMIN_PERM: "yes", DELETE_PGBOUNCER_PERM: "yes", @@ -155,7 +151,6 @@ func initializePerms() { SHOW_BACKUP_PERM: "yes", SHOW_CLUSTER_PERM: "yes", SHOW_CONFIG_PERM: "yes", - SHOW_INGEST_PERM: "yes", SHOW_NAMESPACE_PERM: "yes", SHOW_PGADMIN_PERM: "yes", SHOW_PGBOUNCER_PERM: "yes", From 2b75d18827442f4d105c77a2b8455624c1fc30ea Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Thu, 14 Jan 2021 10:38:46 -0500 Subject: [PATCH 146/276] Allow for Postgres system account user passwords to be updated This introduces the "--set-system-account-password" flag to allow for one to update the password for a PostgreSQL system account user. The flag allows for an override as well as a safety mechanism for one to think about the action they are going to partake in. Issue: #2169 --- cmd/pgo/cmd/update.go | 1 + cmd/pgo/cmd/user.go | 37 ++++++++++--------- .../pgo-client/reference/pgo_update_user.md | 35 +++++++++--------- internal/apiserver/userservice/userimpl.go | 5 ++- pkg/apiservermsgs/usermsgs.go | 5 ++- 5 files changed, 46 insertions(+), 37 deletions(-) diff --git a/cmd/pgo/cmd/update.go b/cmd/pgo/cmd/update.go index d09c506c7b..d16efba880 100644 --- a/cmd/pgo/cmd/update.go +++ b/cmd/pgo/cmd/update.go @@ -187,6 +187,7 @@ func init() { UpdateUserCmd.Flags().BoolVar(&PasswordValidAlways, "valid-always", false, "Sets a password to never expire based on expiration time. Takes precedence over --valid-days") UpdateUserCmd.Flags().BoolVar(&RotatePassword, "rotate-password", false, "Rotates the user's password with an automatically generated password. The length of the password is determine by either --password-length or the value set on the server, in that order.") UpdateUserCmd.Flags().StringVarP(&Selector, "selector", "s", "", "The selector to use for cluster filtering.") + UpdateUserCmd.Flags().BoolVar(&ShowSystemAccounts, "set-system-account-password", false, "Allows for a system account password to be set.") } // UpdateCmd represents the update command diff --git a/cmd/pgo/cmd/user.go b/cmd/pgo/cmd/user.go index 3c61d5671e..bc8f9352ae 100644 --- a/cmd/pgo/cmd/user.go +++ b/cmd/pgo/cmd/user.go @@ -55,7 +55,8 @@ var PasswordLength int var PasswordValidAlways bool // ShowSystemAccounts enables the display of the PostgreSQL user accounts that -// perform system functions, such as the "postgres" user +// perform system functions, such as the "postgres" user, and for taking action +// on these accounts var ShowSystemAccounts bool func createUser(args []string, ns string) { @@ -366,20 +367,21 @@ func showUser(args []string, ns string) { func updateUser(clusterNames []string, namespace string) { // set up the reuqest request := msgs.UpdateUserRequest{ - AllFlag: AllFlag, - Clusters: clusterNames, - Expired: Expired, - ExpireUser: ExpireUser, - ManagedUser: ManagedUser, - Namespace: namespace, - Password: Password, - PasswordAgeDays: PasswordAgeDays, - PasswordLength: PasswordLength, - PasswordValidAlways: PasswordValidAlways, - PasswordType: PasswordType, - RotatePassword: RotatePassword, - Selector: Selector, - Username: strings.TrimSpace(Username), + AllFlag: AllFlag, + Clusters: clusterNames, + Expired: Expired, + ExpireUser: ExpireUser, + ManagedUser: ManagedUser, + Namespace: namespace, + Password: Password, + PasswordAgeDays: PasswordAgeDays, + PasswordLength: PasswordLength, + PasswordValidAlways: PasswordValidAlways, + PasswordType: PasswordType, + RotatePassword: RotatePassword, + Selector: Selector, + SetSystemAccountPassword: ShowSystemAccounts, + Username: strings.TrimSpace(Username), } // check to see if EnableLogin or DisableLogin is set. If so, set a value @@ -391,8 +393,9 @@ func updateUser(clusterNames []string, namespace string) { } // check to see if this is a system account if a user name is passed in - if request.Username != "" && utiloperator.IsPostgreSQLUserSystemAccount(request.Username) { - fmt.Println("Error:", request.Username, "is a system account and cannot be used") + if request.Username != "" && utiloperator.IsPostgreSQLUserSystemAccount(request.Username) && !request.SetSystemAccountPassword { + fmt.Println("Error:", request.Username, "is a system account and cannot be used. "+ + "You can override this with the \"--set-system-account-password\" flag.") os.Exit(1) } diff --git a/docs/content/pgo-client/reference/pgo_update_user.md b/docs/content/pgo-client/reference/pgo_update_user.md index 25c18b73da..5678720621 100644 --- a/docs/content/pgo-client/reference/pgo_update_user.md +++ b/docs/content/pgo-client/reference/pgo_update_user.md @@ -32,27 +32,28 @@ pgo update user [flags] ### Options ``` - --all all clusters. - --disable-login Disables a PostgreSQL user from being able to log into the PostgreSQL cluster. - --enable-login Enables a PostgreSQL user to be able to log into the PostgreSQL cluster. - --expire-user Performs expiring a user if set to true. - --expired int Updates passwords that will expire in X days using an autogenerated password. - -h, --help help for user - -o, --output string The output format. Supported types are: "json" - --password string Specifies the user password when updating a user password or creating a new user. If --rotate-password is set as well, --password takes precedence. - --password-length int If no password is supplied, sets the length of the automatically generated password. Defaults to the value set on the server. - --password-type string The type of password hashing to use.Choices are: (md5, scram-sha-256). This only takes effect if the password is being changed. (default "md5") - --rotate-password Rotates the user's password with an automatically generated password. The length of the password is determine by either --password-length or the value set on the server, in that order. - -s, --selector string The selector to use for cluster filtering. - --username string Updates the postgres user on selective clusters. - --valid-always Sets a password to never expire based on expiration time. Takes precedence over --valid-days - --valid-days int Sets the number of days that a password is valid. Defaults to the server value. + --all all clusters. + --disable-login Disables a PostgreSQL user from being able to log into the PostgreSQL cluster. + --enable-login Enables a PostgreSQL user to be able to log into the PostgreSQL cluster. + --expire-user Performs expiring a user if set to true. + --expired int Updates passwords that will expire in X days using an autogenerated password. + -h, --help help for user + -o, --output string The output format. Supported types are: "json" + --password string Specifies the user password when updating a user password or creating a new user. If --rotate-password is set as well, --password takes precedence. + --password-length int If no password is supplied, sets the length of the automatically generated password. Defaults to the value set on the server. + --password-type string The type of password hashing to use.Choices are: (md5, scram-sha-256). This only takes effect if the password is being changed. (default "md5") + --rotate-password Rotates the user's password with an automatically generated password. The length of the password is determine by either --password-length or the value set on the server, in that order. + -s, --selector string The selector to use for cluster filtering. + --set-system-account-password Allows for a system account password to be set. + --username string Updates the postgres user on selective clusters. + --valid-always Sets a password to never expire based on expiration time. Takes precedence over --valid-days + --valid-days int Sets the number of days that a password is valid. Defaults to the server value. ``` ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -66,4 +67,4 @@ pgo update user [flags] * [pgo update](/pgo-client/reference/pgo_update/) - Update a pgouser, pgorole, or cluster -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/internal/apiserver/userservice/userimpl.go b/internal/apiserver/userservice/userimpl.go index 2372edc519..8c919859b4 100644 --- a/internal/apiserver/userservice/userimpl.go +++ b/internal/apiserver/userservice/userimpl.go @@ -593,9 +593,10 @@ func UpdateUser(request *msgs.UpdateUserRequest, pgouser string) msgs.UpdateUser // if this involes updating a specific PostgreSQL account, and it is a system // account, return here - if request.Username != "" && util.IsPostgreSQLUserSystemAccount(request.Username) { + if request.Username != "" && util.IsPostgreSQLUserSystemAccount(request.Username) && !request.SetSystemAccountPassword { response.Status.Code = msgs.Error - response.Status.Msg = fmt.Sprintf(errSystemAccountFormat, request.Username) + response.Status.Msg = fmt.Sprintf(errSystemAccountFormat, request.Username) + + " You can override this with the \"--set-system-account-password\" flag." return response } diff --git a/pkg/apiservermsgs/usermsgs.go b/pkg/apiservermsgs/usermsgs.go index 4a716966ba..5de550711c 100644 --- a/pkg/apiservermsgs/usermsgs.go +++ b/pkg/apiservermsgs/usermsgs.go @@ -129,7 +129,10 @@ type UpdateUserRequest struct { PasswordValidAlways bool RotatePassword bool Selector string - Username string + // SetSystemAccountPassword allows one to override the password for a + // designated system account + SetSystemAccountPassword bool + Username string } // UpdateUserResponse contains the response after an update user request From c8427ae7d5f59fda01f88e4875ac98654b585489 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Thu, 14 Jan 2021 10:41:30 -0500 Subject: [PATCH 147/276] Update `pgo` client reference docs The full run had not been done in awhile. --- .../content/pgo-client/reference/pgo_apply.md | 4 ++-- .../pgo-client/reference/pgo_backup.md | 2 +- docs/content/pgo-client/reference/pgo_cat.md | 4 ++-- .../pgo-client/reference/pgo_create.md | 4 ++-- .../reference/pgo_create_cluster.md | 2 +- .../reference/pgo_create_namespace.md | 4 ++-- .../reference/pgo_create_pgadmin.md | 4 ++-- .../reference/pgo_create_pgbouncer.md | 2 +- .../reference/pgo_create_pgorole.md | 4 ++-- .../reference/pgo_create_pgouser.md | 4 ++-- .../pgo-client/reference/pgo_create_policy.md | 2 +- .../reference/pgo_create_schedule.md | 4 ++-- .../pgo-client/reference/pgo_create_user.md | 4 ++-- .../pgo-client/reference/pgo_delete.md | 2 +- .../pgo-client/reference/pgo_delete_backup.md | 2 +- .../reference/pgo_delete_cluster.md | 4 ++-- .../pgo-client/reference/pgo_delete_label.md | 4 ++-- .../reference/pgo_delete_namespace.md | 4 ++-- .../reference/pgo_delete_pgadmin.md | 4 ++-- .../reference/pgo_delete_pgbouncer.md | 4 ++-- .../reference/pgo_delete_pgorole.md | 4 ++-- .../reference/pgo_delete_pgouser.md | 4 ++-- .../pgo-client/reference/pgo_delete_policy.md | 4 ++-- .../reference/pgo_delete_schedule.md | 4 ++-- .../pgo-client/reference/pgo_delete_user.md | 4 ++-- docs/content/pgo-client/reference/pgo_df.md | 4 ++-- .../pgo-client/reference/pgo_failover.md | 2 +- .../content/pgo-client/reference/pgo_label.md | 4 ++-- .../pgo-client/reference/pgo_reload.md | 4 ++-- .../pgo-client/reference/pgo_restart.md | 2 +- .../pgo-client/reference/pgo_restore.md | 2 +- .../content/pgo-client/reference/pgo_scale.md | 23 ++++++++++--------- .../pgo-client/reference/pgo_scaledown.md | 4 ++-- docs/content/pgo-client/reference/pgo_show.md | 4 ++-- .../pgo-client/reference/pgo_show_backup.md | 4 ++-- .../pgo-client/reference/pgo_show_cluster.md | 4 ++-- .../pgo-client/reference/pgo_show_config.md | 4 ++-- .../reference/pgo_show_namespace.md | 4 ++-- .../pgo-client/reference/pgo_show_pgadmin.md | 4 ++-- .../reference/pgo_show_pgbouncer.md | 4 ++-- .../pgo-client/reference/pgo_show_pgorole.md | 4 ++-- .../pgo-client/reference/pgo_show_pgouser.md | 4 ++-- .../pgo-client/reference/pgo_show_policy.md | 4 ++-- .../pgo-client/reference/pgo_show_pvc.md | 4 ++-- .../pgo-client/reference/pgo_show_schedule.md | 4 ++-- .../pgo-client/reference/pgo_show_user.md | 4 ++-- .../pgo-client/reference/pgo_show_workflow.md | 4 ++-- .../pgo-client/reference/pgo_status.md | 4 ++-- docs/content/pgo-client/reference/pgo_test.md | 4 ++-- .../pgo-client/reference/pgo_update.md | 4 ++-- .../reference/pgo_update_cluster.md | 2 +- .../reference/pgo_update_namespace.md | 4 ++-- .../reference/pgo_update_pgbouncer.md | 2 +- .../reference/pgo_update_pgorole.md | 4 ++-- .../reference/pgo_update_pgouser.md | 4 ++-- .../pgo-client/reference/pgo_upgrade.md | 2 +- .../pgo-client/reference/pgo_version.md | 4 ++-- .../content/pgo-client/reference/pgo_watch.md | 4 ++-- 58 files changed, 114 insertions(+), 113 deletions(-) diff --git a/docs/content/pgo-client/reference/pgo_apply.md b/docs/content/pgo-client/reference/pgo_apply.md index 403d6c9d47..8cb65da368 100644 --- a/docs/content/pgo-client/reference/pgo_apply.md +++ b/docs/content/pgo-client/reference/pgo_apply.md @@ -28,7 +28,7 @@ pgo apply [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -42,4 +42,4 @@ pgo apply [flags] * [pgo](/pgo-client/reference/pgo/) - The pgo command line interface. -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_backup.md b/docs/content/pgo-client/reference/pgo_backup.md index d8e028bf57..bca8d77396 100644 --- a/docs/content/pgo-client/reference/pgo_backup.md +++ b/docs/content/pgo-client/reference/pgo_backup.md @@ -44,4 +44,4 @@ pgo backup [flags] * [pgo](/pgo-client/reference/pgo/) - The pgo command line interface. -###### Auto generated by spf13/cobra on 30-Dec-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_cat.md b/docs/content/pgo-client/reference/pgo_cat.md index cef3887e31..0b4a13747f 100644 --- a/docs/content/pgo-client/reference/pgo_cat.md +++ b/docs/content/pgo-client/reference/pgo_cat.md @@ -24,7 +24,7 @@ pgo cat [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -38,4 +38,4 @@ pgo cat [flags] * [pgo](/pgo-client/reference/pgo/) - The pgo command line interface. -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_create.md b/docs/content/pgo-client/reference/pgo_create.md index 14cc07b5d0..2bd589dd40 100644 --- a/docs/content/pgo-client/reference/pgo_create.md +++ b/docs/content/pgo-client/reference/pgo_create.md @@ -30,7 +30,7 @@ pgo create [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -53,4 +53,4 @@ pgo create [flags] * [pgo create schedule](/pgo-client/reference/pgo_create_schedule/) - Create a cron-like scheduled task * [pgo create user](/pgo-client/reference/pgo_create_user/) - Create a PostgreSQL user -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_create_cluster.md b/docs/content/pgo-client/reference/pgo_create_cluster.md index 26ee79dcfc..9993e29127 100644 --- a/docs/content/pgo-client/reference/pgo_create_cluster.md +++ b/docs/content/pgo-client/reference/pgo_create_cluster.md @@ -136,4 +136,4 @@ pgo create cluster [flags] * [pgo create](/pgo-client/reference/pgo_create/) - Create a Postgres Operator resource -###### Auto generated by spf13/cobra on 2-Jan-2021 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_create_namespace.md b/docs/content/pgo-client/reference/pgo_create_namespace.md index 90894e2b77..bf544aba72 100644 --- a/docs/content/pgo-client/reference/pgo_create_namespace.md +++ b/docs/content/pgo-client/reference/pgo_create_namespace.md @@ -28,7 +28,7 @@ pgo create namespace [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -42,4 +42,4 @@ pgo create namespace [flags] * [pgo create](/pgo-client/reference/pgo_create/) - Create a Postgres Operator resource -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_create_pgadmin.md b/docs/content/pgo-client/reference/pgo_create_pgadmin.md index 1e0c43d578..0dd744cbce 100644 --- a/docs/content/pgo-client/reference/pgo_create_pgadmin.md +++ b/docs/content/pgo-client/reference/pgo_create_pgadmin.md @@ -25,7 +25,7 @@ pgo create pgadmin [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -39,4 +39,4 @@ pgo create pgadmin [flags] * [pgo create](/pgo-client/reference/pgo_create/) - Create a Postgres Operator resource -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_create_pgbouncer.md b/docs/content/pgo-client/reference/pgo_create_pgbouncer.md index beef67d591..c0e10c6b41 100644 --- a/docs/content/pgo-client/reference/pgo_create_pgbouncer.md +++ b/docs/content/pgo-client/reference/pgo_create_pgbouncer.md @@ -46,4 +46,4 @@ pgo create pgbouncer [flags] * [pgo create](/pgo-client/reference/pgo_create/) - Create a Postgres Operator resource -###### Auto generated by spf13/cobra on 1-Jan-2021 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_create_pgorole.md b/docs/content/pgo-client/reference/pgo_create_pgorole.md index 50bcc66915..5320296bb4 100644 --- a/docs/content/pgo-client/reference/pgo_create_pgorole.md +++ b/docs/content/pgo-client/reference/pgo_create_pgorole.md @@ -25,7 +25,7 @@ pgo create pgorole [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -39,4 +39,4 @@ pgo create pgorole [flags] * [pgo create](/pgo-client/reference/pgo_create/) - Create a Postgres Operator resource -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_create_pgouser.md b/docs/content/pgo-client/reference/pgo_create_pgouser.md index 35513ea915..aa5d39b5eb 100644 --- a/docs/content/pgo-client/reference/pgo_create_pgouser.md +++ b/docs/content/pgo-client/reference/pgo_create_pgouser.md @@ -28,7 +28,7 @@ pgo create pgouser [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -42,4 +42,4 @@ pgo create pgouser [flags] * [pgo create](/pgo-client/reference/pgo_create/) - Create a Postgres Operator resource -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_create_policy.md b/docs/content/pgo-client/reference/pgo_create_policy.md index 7705067853..0bce879654 100644 --- a/docs/content/pgo-client/reference/pgo_create_policy.md +++ b/docs/content/pgo-client/reference/pgo_create_policy.md @@ -39,4 +39,4 @@ pgo create policy [flags] * [pgo create](/pgo-client/reference/pgo_create/) - Create a Postgres Operator resource -###### Auto generated by spf13/cobra on 21-Dec-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_create_schedule.md b/docs/content/pgo-client/reference/pgo_create_schedule.md index 6549cfe588..d303b28488 100644 --- a/docs/content/pgo-client/reference/pgo_create_schedule.md +++ b/docs/content/pgo-client/reference/pgo_create_schedule.md @@ -34,7 +34,7 @@ pgo create schedule [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -48,4 +48,4 @@ pgo create schedule [flags] * [pgo create](/pgo-client/reference/pgo_create/) - Create a Postgres Operator resource -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_create_user.md b/docs/content/pgo-client/reference/pgo_create_user.md index cd38c71059..106b27a59f 100644 --- a/docs/content/pgo-client/reference/pgo_create_user.md +++ b/docs/content/pgo-client/reference/pgo_create_user.md @@ -36,7 +36,7 @@ pgo create user [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -50,4 +50,4 @@ pgo create user [flags] * [pgo create](/pgo-client/reference/pgo_create/) - Create a Postgres Operator resource -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_delete.md b/docs/content/pgo-client/reference/pgo_delete.md index ea25f615b0..1eb47af507 100644 --- a/docs/content/pgo-client/reference/pgo_delete.md +++ b/docs/content/pgo-client/reference/pgo_delete.md @@ -64,4 +64,4 @@ pgo delete [flags] * [pgo delete schedule](/pgo-client/reference/pgo_delete_schedule/) - Delete a schedule * [pgo delete user](/pgo-client/reference/pgo_delete_user/) - Delete a user -###### Auto generated by spf13/cobra on 20-Dec-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_delete_backup.md b/docs/content/pgo-client/reference/pgo_delete_backup.md index 67b40d1c53..9b1f091bde 100644 --- a/docs/content/pgo-client/reference/pgo_delete_backup.md +++ b/docs/content/pgo-client/reference/pgo_delete_backup.md @@ -40,4 +40,4 @@ pgo delete backup [flags] * [pgo delete](/pgo-client/reference/pgo_delete/) - Delete an Operator resource -###### Auto generated by spf13/cobra on 20-Dec-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_delete_cluster.md b/docs/content/pgo-client/reference/pgo_delete_cluster.md index bf550cf53e..0243dc8c3c 100644 --- a/docs/content/pgo-client/reference/pgo_delete_cluster.md +++ b/docs/content/pgo-client/reference/pgo_delete_cluster.md @@ -30,7 +30,7 @@ pgo delete cluster [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -44,4 +44,4 @@ pgo delete cluster [flags] * [pgo delete](/pgo-client/reference/pgo_delete/) - Delete an Operator resource -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_delete_label.md b/docs/content/pgo-client/reference/pgo_delete_label.md index b8ad151b73..2e8efeb5c3 100644 --- a/docs/content/pgo-client/reference/pgo_delete_label.md +++ b/docs/content/pgo-client/reference/pgo_delete_label.md @@ -28,7 +28,7 @@ pgo delete label [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -42,4 +42,4 @@ pgo delete label [flags] * [pgo delete](/pgo-client/reference/pgo_delete/) - Delete an Operator resource -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_delete_namespace.md b/docs/content/pgo-client/reference/pgo_delete_namespace.md index 63e9fa95db..a339bf0218 100644 --- a/docs/content/pgo-client/reference/pgo_delete_namespace.md +++ b/docs/content/pgo-client/reference/pgo_delete_namespace.md @@ -25,7 +25,7 @@ pgo delete namespace [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -39,4 +39,4 @@ pgo delete namespace [flags] * [pgo delete](/pgo-client/reference/pgo_delete/) - Delete an Operator resource -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_delete_pgadmin.md b/docs/content/pgo-client/reference/pgo_delete_pgadmin.md index d48bacd9d0..5f778c2eb5 100644 --- a/docs/content/pgo-client/reference/pgo_delete_pgadmin.md +++ b/docs/content/pgo-client/reference/pgo_delete_pgadmin.md @@ -26,7 +26,7 @@ pgo delete pgadmin [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -40,4 +40,4 @@ pgo delete pgadmin [flags] * [pgo delete](/pgo-client/reference/pgo_delete/) - Delete an Operator resource -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_delete_pgbouncer.md b/docs/content/pgo-client/reference/pgo_delete_pgbouncer.md index bcf71def78..b1524b1a78 100644 --- a/docs/content/pgo-client/reference/pgo_delete_pgbouncer.md +++ b/docs/content/pgo-client/reference/pgo_delete_pgbouncer.md @@ -27,7 +27,7 @@ pgo delete pgbouncer [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -41,4 +41,4 @@ pgo delete pgbouncer [flags] * [pgo delete](/pgo-client/reference/pgo_delete/) - Delete an Operator resource -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_delete_pgorole.md b/docs/content/pgo-client/reference/pgo_delete_pgorole.md index f67359235d..682baf156e 100644 --- a/docs/content/pgo-client/reference/pgo_delete_pgorole.md +++ b/docs/content/pgo-client/reference/pgo_delete_pgorole.md @@ -26,7 +26,7 @@ pgo delete pgorole [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -40,4 +40,4 @@ pgo delete pgorole [flags] * [pgo delete](/pgo-client/reference/pgo_delete/) - Delete an Operator resource -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_delete_pgouser.md b/docs/content/pgo-client/reference/pgo_delete_pgouser.md index 0a4bba911f..2bddabd0e6 100644 --- a/docs/content/pgo-client/reference/pgo_delete_pgouser.md +++ b/docs/content/pgo-client/reference/pgo_delete_pgouser.md @@ -26,7 +26,7 @@ pgo delete pgouser [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -40,4 +40,4 @@ pgo delete pgouser [flags] * [pgo delete](/pgo-client/reference/pgo_delete/) - Delete an Operator resource -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_delete_policy.md b/docs/content/pgo-client/reference/pgo_delete_policy.md index cf40d26835..5f565e764b 100644 --- a/docs/content/pgo-client/reference/pgo_delete_policy.md +++ b/docs/content/pgo-client/reference/pgo_delete_policy.md @@ -26,7 +26,7 @@ pgo delete policy [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -40,4 +40,4 @@ pgo delete policy [flags] * [pgo delete](/pgo-client/reference/pgo_delete/) - Delete an Operator resource -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_delete_schedule.md b/docs/content/pgo-client/reference/pgo_delete_schedule.md index b7de536bbd..600a797d11 100644 --- a/docs/content/pgo-client/reference/pgo_delete_schedule.md +++ b/docs/content/pgo-client/reference/pgo_delete_schedule.md @@ -29,7 +29,7 @@ pgo delete schedule [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -43,4 +43,4 @@ pgo delete schedule [flags] * [pgo delete](/pgo-client/reference/pgo_delete/) - Delete an Operator resource -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_delete_user.md b/docs/content/pgo-client/reference/pgo_delete_user.md index ea4f7f75ae..48cef7d07c 100644 --- a/docs/content/pgo-client/reference/pgo_delete_user.md +++ b/docs/content/pgo-client/reference/pgo_delete_user.md @@ -29,7 +29,7 @@ pgo delete user [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -43,4 +43,4 @@ pgo delete user [flags] * [pgo delete](/pgo-client/reference/pgo_delete/) - Delete an Operator resource -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_df.md b/docs/content/pgo-client/reference/pgo_df.md index 3a744dfbe9..7b81786c3a 100644 --- a/docs/content/pgo-client/reference/pgo_df.md +++ b/docs/content/pgo-client/reference/pgo_df.md @@ -29,7 +29,7 @@ pgo df [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -43,4 +43,4 @@ pgo df [flags] * [pgo](/pgo-client/reference/pgo/) - The pgo command line interface. -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_failover.md b/docs/content/pgo-client/reference/pgo_failover.md index c81b3bfd92..deca90ecc7 100644 --- a/docs/content/pgo-client/reference/pgo_failover.md +++ b/docs/content/pgo-client/reference/pgo_failover.md @@ -47,4 +47,4 @@ pgo failover [flags] * [pgo](/pgo-client/reference/pgo/) - The pgo command line interface. -###### Auto generated by spf13/cobra on 4-Jan-2021 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_label.md b/docs/content/pgo-client/reference/pgo_label.md index 14f6486ad7..abcb3e0115 100644 --- a/docs/content/pgo-client/reference/pgo_label.md +++ b/docs/content/pgo-client/reference/pgo_label.md @@ -30,7 +30,7 @@ pgo label [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -44,4 +44,4 @@ pgo label [flags] * [pgo](/pgo-client/reference/pgo/) - The pgo command line interface. -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_reload.md b/docs/content/pgo-client/reference/pgo_reload.md index ebc8dc2e1a..f6191cfe17 100644 --- a/docs/content/pgo-client/reference/pgo_reload.md +++ b/docs/content/pgo-client/reference/pgo_reload.md @@ -26,7 +26,7 @@ pgo reload [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -40,4 +40,4 @@ pgo reload [flags] * [pgo](/pgo-client/reference/pgo/) - The pgo command line interface. -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_restart.md b/docs/content/pgo-client/reference/pgo_restart.md index 2a56f8ed12..0a1b3e3f4c 100644 --- a/docs/content/pgo-client/reference/pgo_restart.md +++ b/docs/content/pgo-client/reference/pgo_restart.md @@ -53,4 +53,4 @@ pgo restart [flags] * [pgo](/pgo-client/reference/pgo/) - The pgo command line interface. -###### Auto generated by spf13/cobra on 5-Dec-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_restore.md b/docs/content/pgo-client/reference/pgo_restore.md index 78d3584e7e..4894154e73 100644 --- a/docs/content/pgo-client/reference/pgo_restore.md +++ b/docs/content/pgo-client/reference/pgo_restore.md @@ -47,4 +47,4 @@ pgo restore [flags] * [pgo](/pgo-client/reference/pgo/) - The pgo command line interface. -###### Auto generated by spf13/cobra on 31-Dec-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_scale.md b/docs/content/pgo-client/reference/pgo_scale.md index 146645d08c..edcd61aab5 100644 --- a/docs/content/pgo-client/reference/pgo_scale.md +++ b/docs/content/pgo-client/reference/pgo_scale.md @@ -18,16 +18,17 @@ pgo scale [flags] ### Options ``` - --ccp-image-tag string The CCPImageTag to use for cluster creation. If specified, overrides the .pgo.yaml setting. - -h, --help help for scale - --no-prompt No command line confirmation. - --node-label string The node label (key) to use in placing the replica database. If not set, any node is used. - --replica-count int The replica count to apply to the clusters. (default 1) - --service-type string The service type to use in the replica Service. If not set, the default in pgo.yaml will be used. - --storage-config string The name of a Storage config in pgo.yaml to use for the replica storage. - --toleration strings Set Pod tolerations for each PostgreSQL instance in a cluster. - The general format is "key=value:Effect" - For example, to add an Exists and an Equals toleration: "--toleration=ssd:NoSchedule,zone=east:NoSchedule" + --ccp-image-tag string The CCPImageTag to use for cluster creation. If specified, overrides the .pgo.yaml setting. + -h, --help help for scale + --no-prompt No command line confirmation. + --node-affinity-type string Sets the type of node affinity to use. Can be either preferred (default) or required. Must be used with --node-label + --node-label string The node label (key) to use in placing the replica database. If not set, any node is used. + --replica-count int The replica count to apply to the clusters. (default 1) + --service-type string The service type to use in the replica Service. If not set, the default in pgo.yaml will be used. + --storage-config string The name of a Storage config in pgo.yaml to use for the replica storage. + --toleration strings Set Pod tolerations for each PostgreSQL instance in a cluster. + The general format is "key=value:Effect" + For example, to add an Exists and an Equals toleration: "--toleration=ssd:NoSchedule,zone=east:NoSchedule" ``` ### Options inherited from parent commands @@ -47,4 +48,4 @@ pgo scale [flags] * [pgo](/pgo-client/reference/pgo/) - The pgo command line interface. -###### Auto generated by spf13/cobra on 25-Dec-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_scaledown.md b/docs/content/pgo-client/reference/pgo_scaledown.md index deef6123d9..157e24ba12 100644 --- a/docs/content/pgo-client/reference/pgo_scaledown.md +++ b/docs/content/pgo-client/reference/pgo_scaledown.md @@ -32,7 +32,7 @@ pgo scaledown [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -46,4 +46,4 @@ pgo scaledown [flags] * [pgo](/pgo-client/reference/pgo/) - The pgo command line interface. -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_show.md b/docs/content/pgo-client/reference/pgo_show.md index b71032f999..5a4e47350e 100644 --- a/docs/content/pgo-client/reference/pgo_show.md +++ b/docs/content/pgo-client/reference/pgo_show.md @@ -33,7 +33,7 @@ pgo show [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -60,4 +60,4 @@ pgo show [flags] * [pgo show user](/pgo-client/reference/pgo_show_user/) - Show user information * [pgo show workflow](/pgo-client/reference/pgo_show_workflow/) - Show workflow information -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_show_backup.md b/docs/content/pgo-client/reference/pgo_show_backup.md index a15c426d54..adbb331666 100644 --- a/docs/content/pgo-client/reference/pgo_show_backup.md +++ b/docs/content/pgo-client/reference/pgo_show_backup.md @@ -25,7 +25,7 @@ pgo show backup [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -39,4 +39,4 @@ pgo show backup [flags] * [pgo show](/pgo-client/reference/pgo_show/) - Show the description of a cluster -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_show_cluster.md b/docs/content/pgo-client/reference/pgo_show_cluster.md index 291d3b6ff6..b21ad78830 100644 --- a/docs/content/pgo-client/reference/pgo_show_cluster.md +++ b/docs/content/pgo-client/reference/pgo_show_cluster.md @@ -29,7 +29,7 @@ pgo show cluster [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -43,4 +43,4 @@ pgo show cluster [flags] * [pgo show](/pgo-client/reference/pgo_show/) - Show the description of a cluster -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_show_config.md b/docs/content/pgo-client/reference/pgo_show_config.md index ae3cb75059..65e8efea4f 100644 --- a/docs/content/pgo-client/reference/pgo_show_config.md +++ b/docs/content/pgo-client/reference/pgo_show_config.md @@ -24,7 +24,7 @@ pgo show config [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -38,4 +38,4 @@ pgo show config [flags] * [pgo show](/pgo-client/reference/pgo_show/) - Show the description of a cluster -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_show_namespace.md b/docs/content/pgo-client/reference/pgo_show_namespace.md index 9794a12bac..40ce90c983 100644 --- a/docs/content/pgo-client/reference/pgo_show_namespace.md +++ b/docs/content/pgo-client/reference/pgo_show_namespace.md @@ -25,7 +25,7 @@ pgo show namespace [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -39,4 +39,4 @@ pgo show namespace [flags] * [pgo show](/pgo-client/reference/pgo_show/) - Show the description of a cluster -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_show_pgadmin.md b/docs/content/pgo-client/reference/pgo_show_pgadmin.md index 73574045aa..f4ebb5d617 100644 --- a/docs/content/pgo-client/reference/pgo_show_pgadmin.md +++ b/docs/content/pgo-client/reference/pgo_show_pgadmin.md @@ -28,7 +28,7 @@ pgo show pgadmin [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -42,4 +42,4 @@ pgo show pgadmin [flags] * [pgo show](/pgo-client/reference/pgo_show/) - Show the description of a cluster -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_show_pgbouncer.md b/docs/content/pgo-client/reference/pgo_show_pgbouncer.md index 0a977097a8..707782ccd5 100644 --- a/docs/content/pgo-client/reference/pgo_show_pgbouncer.md +++ b/docs/content/pgo-client/reference/pgo_show_pgbouncer.md @@ -28,7 +28,7 @@ pgo show pgbouncer [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -42,4 +42,4 @@ pgo show pgbouncer [flags] * [pgo show](/pgo-client/reference/pgo_show/) - Show the description of a cluster -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_show_pgorole.md b/docs/content/pgo-client/reference/pgo_show_pgorole.md index f8241d4e33..ca967aaeb6 100644 --- a/docs/content/pgo-client/reference/pgo_show_pgorole.md +++ b/docs/content/pgo-client/reference/pgo_show_pgorole.md @@ -25,7 +25,7 @@ pgo show pgorole [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -39,4 +39,4 @@ pgo show pgorole [flags] * [pgo show](/pgo-client/reference/pgo_show/) - Show the description of a cluster -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_show_pgouser.md b/docs/content/pgo-client/reference/pgo_show_pgouser.md index 4881d2f1fb..1ad60b4303 100644 --- a/docs/content/pgo-client/reference/pgo_show_pgouser.md +++ b/docs/content/pgo-client/reference/pgo_show_pgouser.md @@ -25,7 +25,7 @@ pgo show pgouser [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -39,4 +39,4 @@ pgo show pgouser [flags] * [pgo show](/pgo-client/reference/pgo_show/) - Show the description of a cluster -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_show_policy.md b/docs/content/pgo-client/reference/pgo_show_policy.md index ddeaedbd09..6303392491 100644 --- a/docs/content/pgo-client/reference/pgo_show_policy.md +++ b/docs/content/pgo-client/reference/pgo_show_policy.md @@ -26,7 +26,7 @@ pgo show policy [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -40,4 +40,4 @@ pgo show policy [flags] * [pgo show](/pgo-client/reference/pgo_show/) - Show the description of a cluster -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_show_pvc.md b/docs/content/pgo-client/reference/pgo_show_pvc.md index ea8312dd36..16d7d3f457 100644 --- a/docs/content/pgo-client/reference/pgo_show_pvc.md +++ b/docs/content/pgo-client/reference/pgo_show_pvc.md @@ -26,7 +26,7 @@ pgo show pvc [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -40,4 +40,4 @@ pgo show pvc [flags] * [pgo show](/pgo-client/reference/pgo_show/) - Show the description of a cluster -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_show_schedule.md b/docs/content/pgo-client/reference/pgo_show_schedule.md index 7d39ac4cff..eb9033fc1c 100644 --- a/docs/content/pgo-client/reference/pgo_show_schedule.md +++ b/docs/content/pgo-client/reference/pgo_show_schedule.md @@ -29,7 +29,7 @@ pgo show schedule [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -43,4 +43,4 @@ pgo show schedule [flags] * [pgo show](/pgo-client/reference/pgo_show/) - Show the description of a cluster -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_show_user.md b/docs/content/pgo-client/reference/pgo_show_user.md index 7dcdfe2b31..653fda1ede 100644 --- a/docs/content/pgo-client/reference/pgo_show_user.md +++ b/docs/content/pgo-client/reference/pgo_show_user.md @@ -31,7 +31,7 @@ pgo show user [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -45,4 +45,4 @@ pgo show user [flags] * [pgo show](/pgo-client/reference/pgo_show/) - Show the description of a cluster -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_show_workflow.md b/docs/content/pgo-client/reference/pgo_show_workflow.md index 28d5f11666..722ad453ea 100644 --- a/docs/content/pgo-client/reference/pgo_show_workflow.md +++ b/docs/content/pgo-client/reference/pgo_show_workflow.md @@ -24,7 +24,7 @@ pgo show workflow [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -38,4 +38,4 @@ pgo show workflow [flags] * [pgo show](/pgo-client/reference/pgo_show/) - Show the description of a cluster -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_status.md b/docs/content/pgo-client/reference/pgo_status.md index 21a4a84464..f25a662bd1 100644 --- a/docs/content/pgo-client/reference/pgo_status.md +++ b/docs/content/pgo-client/reference/pgo_status.md @@ -25,7 +25,7 @@ pgo status [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -39,4 +39,4 @@ pgo status [flags] * [pgo](/pgo-client/reference/pgo/) - The pgo command line interface. -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_test.md b/docs/content/pgo-client/reference/pgo_test.md index 36671b5f6e..690efc389d 100644 --- a/docs/content/pgo-client/reference/pgo_test.md +++ b/docs/content/pgo-client/reference/pgo_test.md @@ -29,7 +29,7 @@ pgo test [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -43,4 +43,4 @@ pgo test [flags] * [pgo](/pgo-client/reference/pgo/) - The pgo command line interface. -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_update.md b/docs/content/pgo-client/reference/pgo_update.md index 669c841701..3045234668 100644 --- a/docs/content/pgo-client/reference/pgo_update.md +++ b/docs/content/pgo-client/reference/pgo_update.md @@ -33,7 +33,7 @@ pgo update [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -53,4 +53,4 @@ pgo update [flags] * [pgo update pgouser](/pgo-client/reference/pgo_update_pgouser/) - Update a pgouser * [pgo update user](/pgo-client/reference/pgo_update_user/) - Update a PostgreSQL user -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_update_cluster.md b/docs/content/pgo-client/reference/pgo_update_cluster.md index 3596be5282..78f1a01ff8 100644 --- a/docs/content/pgo-client/reference/pgo_update_cluster.md +++ b/docs/content/pgo-client/reference/pgo_update_cluster.md @@ -95,4 +95,4 @@ pgo update cluster [flags] * [pgo update](/pgo-client/reference/pgo_update/) - Update a pgouser, pgorole, or cluster -###### Auto generated by spf13/cobra on 3-Jan-2021 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_update_namespace.md b/docs/content/pgo-client/reference/pgo_update_namespace.md index 396cb9d30b..d176bd9dd4 100644 --- a/docs/content/pgo-client/reference/pgo_update_namespace.md +++ b/docs/content/pgo-client/reference/pgo_update_namespace.md @@ -23,7 +23,7 @@ pgo update namespace [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -37,4 +37,4 @@ pgo update namespace [flags] * [pgo update](/pgo-client/reference/pgo_update/) - Update a pgouser, pgorole, or cluster -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_update_pgbouncer.md b/docs/content/pgo-client/reference/pgo_update_pgbouncer.md index b4dd3f0247..b97a9f60c4 100644 --- a/docs/content/pgo-client/reference/pgo_update_pgbouncer.md +++ b/docs/content/pgo-client/reference/pgo_update_pgbouncer.md @@ -50,4 +50,4 @@ pgo update pgbouncer [flags] * [pgo update](/pgo-client/reference/pgo_update/) - Update a pgouser, pgorole, or cluster -###### Auto generated by spf13/cobra on 1-Jan-2021 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_update_pgorole.md b/docs/content/pgo-client/reference/pgo_update_pgorole.md index 3c1706b76a..448a4673dc 100644 --- a/docs/content/pgo-client/reference/pgo_update_pgorole.md +++ b/docs/content/pgo-client/reference/pgo_update_pgorole.md @@ -25,7 +25,7 @@ pgo update pgorole [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -39,4 +39,4 @@ pgo update pgorole [flags] * [pgo update](/pgo-client/reference/pgo_update/) - Update a pgouser, pgorole, or cluster -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_update_pgouser.md b/docs/content/pgo-client/reference/pgo_update_pgouser.md index 2991939cd7..a460b11ece 100644 --- a/docs/content/pgo-client/reference/pgo_update_pgouser.md +++ b/docs/content/pgo-client/reference/pgo_update_pgouser.md @@ -30,7 +30,7 @@ pgo update pgouser [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -44,4 +44,4 @@ pgo update pgouser [flags] * [pgo update](/pgo-client/reference/pgo_update/) - Update a pgouser, pgorole, or cluster -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_upgrade.md b/docs/content/pgo-client/reference/pgo_upgrade.md index 534790f189..7ed0c642c9 100644 --- a/docs/content/pgo-client/reference/pgo_upgrade.md +++ b/docs/content/pgo-client/reference/pgo_upgrade.md @@ -44,4 +44,4 @@ pgo upgrade [flags] * [pgo](/pgo-client/reference/pgo/) - The pgo command line interface. -###### Auto generated by spf13/cobra on 20-Dec-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_version.md b/docs/content/pgo-client/reference/pgo_version.md index 5bf407bc73..cdadd2cf95 100644 --- a/docs/content/pgo-client/reference/pgo_version.md +++ b/docs/content/pgo-client/reference/pgo_version.md @@ -25,7 +25,7 @@ pgo version [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -39,4 +39,4 @@ pgo version [flags] * [pgo](/pgo-client/reference/pgo/) - The pgo command line interface. -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_watch.md b/docs/content/pgo-client/reference/pgo_watch.md index 0f3e721545..35416c3b1a 100644 --- a/docs/content/pgo-client/reference/pgo_watch.md +++ b/docs/content/pgo-client/reference/pgo_watch.md @@ -25,7 +25,7 @@ pgo watch [flags] ### Options inherited from parent commands ``` - --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. + --apiserver-url string The URL for the PostgreSQL Operator apiserver that will process the request from the pgo client. Note that the URL should **not** end in a '/'. --debug Enable additional output for debugging. --disable-tls Disable TLS authentication to the Postgres Operator. --exclude-os-trust Exclude CA certs from OS default trust store @@ -39,4 +39,4 @@ pgo watch [flags] * [pgo](/pgo-client/reference/pgo/) - The pgo command line interface. -###### Auto generated by spf13/cobra on 1-Oct-2020 +###### Auto generated by spf13/cobra on 14-Jan-2021 From 75e7099b9ed3b654b808ab38eda5ac18efed35f0 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Thu, 14 Jan 2021 10:40:11 -0500 Subject: [PATCH 148/276] Bump v4.6.0-beta.3 --- Makefile | 2 +- bin/push-ccp-to-gcr.sh | 2 +- conf/postgres-operator/pgo.yaml | 4 ++-- docs/config.toml | 2 +- docs/content/releases/4.6.0.md | 8 ++++++++ examples/create-by-resource/fromcrd.json | 6 +++--- examples/envs.sh | 2 +- examples/helm/README.md | 2 +- examples/helm/postgres/Chart.yaml | 2 +- examples/helm/postgres/templates/pgcluster.yaml | 2 +- examples/helm/postgres/values.yaml | 2 +- examples/kustomize/createcluster/README.md | 16 ++++++++-------- .../kustomize/createcluster/base/pgcluster.yaml | 6 +++--- .../overlay/staging/hippo-rpl1-pgreplica.yaml | 2 +- installers/ansible/README.md | 2 +- installers/ansible/values.yaml | 6 +++--- installers/gcp-marketplace/Makefile | 2 +- installers/gcp-marketplace/README.md | 2 +- installers/gcp-marketplace/values.yaml | 6 +++--- installers/helm/Chart.yaml | 2 +- installers/helm/values.yaml | 6 +++--- installers/kubectl/client-setup.sh | 2 +- installers/kubectl/postgres-operator-ocp311.yml | 8 ++++---- installers/kubectl/postgres-operator.yml | 8 ++++---- installers/metrics/ansible/README.md | 2 +- installers/metrics/helm/Chart.yaml | 2 +- installers/metrics/helm/helm_template.yaml | 2 +- installers/metrics/helm/values.yaml | 2 +- .../kubectl/postgres-operator-metrics-ocp311.yml | 2 +- .../kubectl/postgres-operator-metrics.yml | 2 +- installers/olm/Makefile | 2 +- pkg/apis/crunchydata.com/v1/doc.go | 8 ++++---- pkg/apiservermsgs/common.go | 2 +- redhat/atomic/help.1 | 2 +- redhat/atomic/help.md | 2 +- 35 files changed, 69 insertions(+), 61 deletions(-) diff --git a/Makefile b/Makefile index 63af50dea4..bdaf771968 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ PGOROOT ?= $(CURDIR) PGO_BASEOS ?= centos8 PGO_IMAGE_PREFIX ?= crunchydata PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) -PGO_VERSION ?= 4.6.0-beta.2 +PGO_VERSION ?= 4.6.0-beta.3 PGO_PG_VERSION ?= 13 PGO_PG_FULLVERSION ?= 13.1 PGO_BACKREST_VERSION ?= 2.31 diff --git a/bin/push-ccp-to-gcr.sh b/bin/push-ccp-to-gcr.sh index 1f3838f374..3c98e3829a 100755 --- a/bin/push-ccp-to-gcr.sh +++ b/bin/push-ccp-to-gcr.sh @@ -16,7 +16,7 @@ GCR_IMAGE_PREFIX=gcr.io/crunchy-dev-test CCP_IMAGE_PREFIX=crunchydata -CCP_IMAGE_TAG=centos8-13.1-4.6.0-beta.2 +CCP_IMAGE_TAG=centos8-13.1-4.6.0-beta.3 IMAGES=( crunchy-prometheus diff --git a/conf/postgres-operator/pgo.yaml b/conf/postgres-operator/pgo.yaml index 28493c8c4a..c30e11f41d 100644 --- a/conf/postgres-operator/pgo.yaml +++ b/conf/postgres-operator/pgo.yaml @@ -2,7 +2,7 @@ Cluster: CCPImagePrefix: registry.developers.crunchydata.com/crunchydata Metrics: false Badger: false - CCPImageTag: centos8-13.1-4.6.0-beta.2 + CCPImageTag: centos8-13.1-4.6.0-beta.3 Port: 5432 PGBadgerPort: 10000 ExporterPort: 9187 @@ -81,4 +81,4 @@ Storage: Pgo: Audit: false PGOImagePrefix: registry.developers.crunchydata.com/crunchydata - PGOImageTag: centos8-4.6.0-beta.2 + PGOImageTag: centos8-4.6.0-beta.3 diff --git a/docs/config.toml b/docs/config.toml index 7975290ef6..83e3dc59e7 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -25,7 +25,7 @@ disableNavChevron = false # set true to hide next/prev chevron, default is false highlightClientSide = false # set true to use highlight.pack.js instead of the default hugo chroma highlighter menushortcutsnewtab = true # set true to open shortcuts links to a new tab/window enableGitInfo = true -operatorVersion = "4.6.0-beta.2" +operatorVersion = "4.6.0-beta.3" postgresVersion = "13.1" postgresVersion13 = "13.1" postgresVersion12 = "13.1" diff --git a/docs/content/releases/4.6.0.md b/docs/content/releases/4.6.0.md index 3f423fe16d..ff1a886735 100644 --- a/docs/content/releases/4.6.0.md +++ b/docs/content/releases/4.6.0.md @@ -19,6 +19,7 @@ The PostgreSQL Operator 4.6.0 release includes the following software versions u - [pgBackRest](https://pgbackrest.org/) is now at version 2.31. - [pgnodemx](https://github.com/CrunchyData/pgnodemx) is now at version 1.0.3 - [Patroni](https://patroni.readthedocs.io/) is now at version 2.0.1 +- [pgBadger](https://github.com/darold/pgbadger) is now at 11.4 The monitoring stack for the PostgreSQL Operator uses upstream components as opposed to repackaging them. These are specified as part of the [PostgreSQL Operator Installer](https://access.crunchydata.com/documentation/postgres-operator/latest/installation/postgres-operator/). We have tested this release with the following versions of each component: @@ -193,6 +194,7 @@ Passing in the [`--process-max`](https://pgbackrest.org/command.html#command-arc - `pgo restore` will now first attempt a [pgBackRest delta restore](https://pgbackrest.org/user-guide.html#restore/option-delta), which can significantly speed up the restore time for large databases. Passing in the [`--process-max`](https://pgbackrest.org/command.html#command-archive-get/category-general/option-process-max) option to `--backup-opts` can help speed up the restore process based upon the amount of CPU you have available. - A pgBackRest backup can now be deleted with `pgo delete backup`. A backup name must be specified with the `--target` flag. Please refer to the [documentation](https://access.crunchydata.com/documentation/postgres-operator/latest/tutorial/disaster-recovery/#deleting-a-backup) for how to use this command. - pgBadger can now be enabled/disabled during the lifetime of a PostgreSQL cluster using the `pgo update --enable-pgbadger` and `pgo update --disable-pgbadger` flag. This can also be modified directly on a custom resource. +- Managed PostgreSQL system accounts and now have their credentials set and rotated with `pgo update user` by including the `--set-system-account-password` flag. Suggested by (@srinathganesh). ## Changes @@ -205,13 +207,16 @@ Passing in the [`--process-max`](https://pgbackrest.org/command.html#command-arc - The `pgo failover` command now works without specifying a target: the candidate to fail over to will be automatically selected. - For clusters that have no healthy instances, `pgo failover` can now force a promotion using the `--force` flag. A `--target` flag must also be specified when using `--force`. - If a predefined custom ConfigMap for a PostgreSQL cluster (`-pgha-config`) is detected at bootstrap time, the Operator will ensure it properly initializes the cluster. +- Deleting a `pgclusters.crunchydata.com` custom resource will now properly delete a PostgreSQL cluster. If the `pgclusters.crunchydata.com` custom resource has the annotations `keep-backups` or `keep-data`, it will keep the backups or keep the PostgreSQL data directory respectively. Reported by Leo Khomenko (@lkhomenk). - PostgreSQL JIT compilation is explicitly disabled on new cluster creation. This prevents a memory leak that has been observed on queries coming from the metrics exporter. - The credentials for the metrics collection user are now available with `pgo show user --show-system-accounts`. - The default user for executing scheduled SQL policies is now the Postgres superuser, instead of the replication user. - Add the `--no-prompt` flag to `pgo upgrade`. The mechanism to disable the prompt verification was already in place, but the flag was not exposed. Reported by (@devopsevd). - Remove certain characters that causes issues in shell environments from consideration when using the random password generator, which is used to create default passwords or with `--rotate-password`. +- Allow for the `--link-map` attribute for a pgBackRest option, which can help with the restore of an existing cluster to a new cluster that adds an external WAL volume. - Remove the long deprecated `archivestorage` attribute from the `pgclusters.crunchydata.com` custom resource definition. As this attribute is not used at all, this should have no effect. - The `ArchiveMode` parameter is now removed from the configuration. This had been fully deprecated for awhile. +- Add an explicit size limit of `64Mi` for the `pgBadger` ephemeral storage mount. Additionally, remove the ephemeral storage mount for the `/recover` mount point as that is not used. Reported by Pierre-Marie Petit (@pmpetit). - New PostgreSQL Operator deployments will now generate ECDSA keys (P-256, SHA384) for use by the API server. ## Fixes @@ -227,9 +232,12 @@ Passing in the [`--process-max`](https://pgbackrest.org/command.html#command-arc - Fix syntax in recovery check command which could lead to failures when manually promoting a standby cluster. Reported by (@SockenSalat). - Fix potential race condition that could lead to a crash in the Operator boot when an error is issued around loading the `pgo-config` ConfigMap. Reported by Aleksander Roszig (@AleksanderRoszig). - Do not trigger a backup if a standby cluster fails over. Reported by (@aprilito1965). +- Ensure pgBouncer Secret is created when adding it to a standby cluster. - Remove legacy `defaultMode` setting on the volume instructions for the pgBackRest repo Secret as the `readOnly` setting is used on the mount itself. Reported by (@szhang1). - The logger no longer defaults to using a log level of `DEBUG`. - Autofailover is no longer disabled when an `rmdata` Job is run, enabling a clean database shutdown process when deleting a PostgreSQL cluster. +- Allow for `Restart` API server permission to be explicitly set. Reported by Aleksander Roszig (@AleksanderRoszig). - Update `pgo-target` permissions to match expectations for modern Kubernetes versions. - Major upgrade container now includes references for `pgnodemx`. - During a major upgrade, ensure permissions are correct on the old data directory before running `pg_upgrade`. +- The metrics stack installer is fixed to work in environments that may not have connectivity to the Internet ("air gapped"). Reported by (@eliranw). diff --git a/examples/create-by-resource/fromcrd.json b/examples/create-by-resource/fromcrd.json index f1f2043f14..d83b9cd810 100644 --- a/examples/create-by-resource/fromcrd.json +++ b/examples/create-by-resource/fromcrd.json @@ -10,7 +10,7 @@ "deployment-name": "fromcrd", "name": "fromcrd", "pg-cluster": "fromcrd", - "pgo-version": "4.6.0-beta.2", + "pgo-version": "4.6.0-beta.3", "pgouser": "pgoadmin" }, "name": "fromcrd", @@ -45,7 +45,7 @@ "supplementalgroups": "" }, "ccpimage": "crunchy-postgres-ha", - "ccpimagetag": "centos8-13.1-4.6.0-beta.2", + "ccpimagetag": "centos8-13.1-4.6.0-beta.3", "clustername": "fromcrd", "database": "userdb", "exporterport": "9187", @@ -60,7 +60,7 @@ "port": "5432", "user": "testuser", "userlabels": { - "pgo-version": "4.6.0-beta.2" + "pgo-version": "4.6.0-beta.3" } } } diff --git a/examples/envs.sh b/examples/envs.sh index 37fa831db8..ebcd9798a0 100644 --- a/examples/envs.sh +++ b/examples/envs.sh @@ -20,7 +20,7 @@ export PGO_CONF_DIR=$PGOROOT/installers/ansible/roles/pgo-operator/files # the version of the Operator you run is set by these vars export PGO_IMAGE_PREFIX=registry.developers.crunchydata.com/crunchydata export PGO_BASEOS=centos8 -export PGO_VERSION=4.6.0-beta.2 +export PGO_VERSION=4.6.0-beta.3 export PGO_IMAGE_TAG=$PGO_BASEOS-$PGO_VERSION # for setting the pgo apiserver port, disabling TLS or not verifying TLS diff --git a/examples/helm/README.md b/examples/helm/README.md index 81fbb97fe8..04ab5211aa 100644 --- a/examples/helm/README.md +++ b/examples/helm/README.md @@ -64,7 +64,7 @@ The following values can also be set: - `ha`: Whether or not to deploy a high availability PostgreSQL cluster. Can be either `true` or `false`, defaults to `false`. - `imagePrefix`: The prefix of the container images to use for this PostgreSQL cluster. Default to `registry.developers.crunchydata.com/crunchydata`. - `image`: The name of the container image to use for the PostgreSQL cluster. Defaults to `crunchy-postgres-ha`. -- `imageTag`: The container image tag to use. Defaults to `centos8-13.1-4.6.0-beta.2`. +- `imageTag`: The container image tag to use. Defaults to `centos8-13.1-4.6.0-beta.3`. - `memory`: The memory limit for the PostgreSQL cluster. Follows standard Kubernetes formatting. - `monitoring`: Whether or not to enable monitoring / metrics collection for this PostgreSQL instance. Can either be `true` or `false`, defaults to `false`. diff --git a/examples/helm/postgres/Chart.yaml b/examples/helm/postgres/Chart.yaml index d2c7a63902..7a6b0a2287 100644 --- a/examples/helm/postgres/Chart.yaml +++ b/examples/helm/postgres/Chart.yaml @@ -20,4 +20,4 @@ version: 0.2.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. -appVersion: 4.6.0-beta.2 +appVersion: 4.6.0-beta.3 diff --git a/examples/helm/postgres/templates/pgcluster.yaml b/examples/helm/postgres/templates/pgcluster.yaml index 563d7246de..886d6d43d5 100644 --- a/examples/helm/postgres/templates/pgcluster.yaml +++ b/examples/helm/postgres/templates/pgcluster.yaml @@ -28,7 +28,7 @@ spec: storagetype: dynamic ccpimage: {{ .Values.image | default "crunchy-postgres-ha" | quote }} ccpimageprefix: {{ .Values.imagePrefix | default "registry.developers.crunchydata.com/crunchydata" | quote }} - ccpimagetag: {{ .Values.imageTag | default "centos8-13.1-4.6.0-beta.2" | quote }} + ccpimagetag: {{ .Values.imageTag | default "centos8-13.1-4.6.0-beta.3" | quote }} clustername: {{ .Values.name | quote }} database: {{ .Values.name | quote }} {{- if .Values.monitoring }} diff --git a/examples/helm/postgres/values.yaml b/examples/helm/postgres/values.yaml index 60b4b3e53b..b1a541dbb3 100644 --- a/examples/helm/postgres/values.yaml +++ b/examples/helm/postgres/values.yaml @@ -10,5 +10,5 @@ password: W4tch0ut4hippo$ # ha: true # imagePrefix: registry.developers.crunchydata.com/crunchydata # image: crunchy-postgres-ha -# imageTag: centos8-13.1-4.6.0-beta.2 +# imageTag: centos8-13.1-4.6.0-beta.3 # memory: 1Gi diff --git a/examples/kustomize/createcluster/README.md b/examples/kustomize/createcluster/README.md index 8cdd77ab04..ba466f62eb 100644 --- a/examples/kustomize/createcluster/README.md +++ b/examples/kustomize/createcluster/README.md @@ -44,13 +44,13 @@ pgo show cluster hippo -n pgo ``` You will see something like this if successful: ``` -cluster : hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.2) +cluster : hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.3) pod : hippo-8fb6bd96-j87wq (Running) on gke-xxxx-default-pool-38e946bd-257w (1/1) (primary) pvc: hippo (1Gi) deployment : hippo deployment : hippo-backrest-shared-repo service : hippo - ClusterIP (10.0.56.86) - Ports (2022/TCP, 5432/TCP) - labels : pgo-version=4.6.0-beta.2 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.0-beta.3 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata ``` Feel free to run other pgo cli commands on the hippo cluster @@ -79,7 +79,7 @@ pgo show cluster dev-hippo -n pgo ``` You will see something like this if successful: ``` -cluster : dev-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.2) +cluster : dev-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.3) pod : dev-hippo-588d4cb746-bwrxb (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: dev-hippo (1Gi) deployment : dev-hippo @@ -87,7 +87,7 @@ cluster : dev-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.2) deployment : dev-hippo-pgbouncer service : dev-hippo - ClusterIP (10.0.62.87) - Ports (2022/TCP, 5432/TCP) service : dev-hippo-pgbouncer - ClusterIP (10.0.48.120) - Ports (5432/TCP) - labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.6.0-beta.2 pgouser=admin + labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.6.0-beta.3 pgouser=admin ``` #### staging The staging overlay will deploy a crunchy postgreSQL cluster with 2 replica's with annotations added @@ -113,7 +113,7 @@ pgo show cluster staging-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : staging-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.2) +cluster : staging-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.3) pod : staging-hippo-85cf6dcb65-9h748 (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: staging-hippo (1Gi) pod : staging-hippo-lnxw-cf47d8c8b-6r4wn (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (replica) @@ -128,7 +128,7 @@ cluster : staging-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.2) service : staging-hippo-replica - ClusterIP (10.0.56.57) - Ports (2022/TCP, 5432/TCP) pgreplica : staging-hippo-lnxw pgreplica : staging-hippo-rpl1 - labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.6.0-beta.2 pgouser=admin vendor=crunchydata + labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.6.0-beta.3 pgouser=admin vendor=crunchydata ``` #### production @@ -154,7 +154,7 @@ pgo show cluster prod-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : prod-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.2) +cluster : prod-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.3) pod : prod-hippo-5d6dd46497-rr67c (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (primary) pvc: prod-hippo (1Gi) pod : prod-hippo-flty-84d97c8769-2pzbh (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (replica) @@ -165,7 +165,7 @@ cluster : prod-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.2) service : prod-hippo - ClusterIP (10.0.56.18) - Ports (2022/TCP, 5432/TCP) service : prod-hippo-replica - ClusterIP (10.0.56.101) - Ports (2022/TCP, 5432/TCP) pgreplica : prod-hippo-flty - labels : pgo-version=4.6.0-beta.2 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.0-beta.3 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata ``` ### Delete the clusters To delete the clusters run the following pgo cli commands diff --git a/examples/kustomize/createcluster/base/pgcluster.yaml b/examples/kustomize/createcluster/base/pgcluster.yaml index f89650796b..00dbc793a7 100644 --- a/examples/kustomize/createcluster/base/pgcluster.yaml +++ b/examples/kustomize/createcluster/base/pgcluster.yaml @@ -10,7 +10,7 @@ metadata: deployment-name: hippo name: hippo pg-cluster: hippo - pgo-version: 4.6.0-beta.2 + pgo-version: 4.6.0-beta.3 pgouser: admin name: hippo namespace: pgo @@ -42,7 +42,7 @@ spec: annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: registry.developers.crunchydata.com/crunchydata - ccpimagetag: centos8-13.1-4.6.0-beta.2 + ccpimagetag: centos8-13.1-4.6.0-beta.3 clustername: hippo customconfig: "" database: hippo @@ -63,4 +63,4 @@ spec: port: "5432" user: hippo userlabels: - pgo-version: 4.6.0-beta.2 + pgo-version: 4.6.0-beta.3 diff --git a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml index 359350bb6f..a9fcb3a2bf 100644 --- a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml +++ b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml @@ -20,4 +20,4 @@ spec: storagetype: dynamic supplementalgroups: "" userlabels: - pgo-version: 4.6.0-beta.2 + pgo-version: 4.6.0-beta.3 diff --git a/installers/ansible/README.md b/installers/ansible/README.md index 4037651766..88b151a5a3 100644 --- a/installers/ansible/README.md +++ b/installers/ansible/README.md @@ -4,7 +4,7 @@ Crunchy Data

-Latest Release: 4.6.0-beta.2 +Latest Release: 4.6.0-beta.3 ## General diff --git a/installers/ansible/values.yaml b/installers/ansible/values.yaml index bc6a9a7048..ad8942500f 100644 --- a/installers/ansible/values.yaml +++ b/installers/ansible/values.yaml @@ -17,7 +17,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.1-4.6.0-beta.2" +ccp_image_tag: "centos8-13.1-4.6.0-beta.3" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -50,14 +50,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.0-beta.2" +pgo_client_version: "4.6.0-beta.3" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos8-4.6.0-beta.2" +pgo_image_tag: "centos8-4.6.0-beta.3" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/gcp-marketplace/Makefile b/installers/gcp-marketplace/Makefile index c344f572e8..d25a30c579 100644 --- a/installers/gcp-marketplace/Makefile +++ b/installers/gcp-marketplace/Makefile @@ -6,7 +6,7 @@ MARKETPLACE_TOOLS ?= gcr.io/cloud-marketplace-tools/k8s/dev:$(MARKETPLACE_VERSIO MARKETPLACE_VERSION ?= 0.9.4 KUBECONFIG ?= $(HOME)/.kube/config PARAMETERS ?= {} -PGO_VERSION ?= 4.6.0-beta.2 +PGO_VERSION ?= 4.6.0-beta.3 IMAGE_BUILD_ARGS = --build-arg MARKETPLACE_VERSION='$(MARKETPLACE_VERSION)' \ --build-arg PGO_VERSION='$(PGO_VERSION)' diff --git a/installers/gcp-marketplace/README.md b/installers/gcp-marketplace/README.md index 4d204df786..b96a602f73 100644 --- a/installers/gcp-marketplace/README.md +++ b/installers/gcp-marketplace/README.md @@ -59,7 +59,7 @@ Google Cloud Marketplace. ```shell IMAGE_REPOSITORY=gcr.io/crunchydata-public/postgres-operator - export PGO_VERSION=4.6.0-beta.2 + export PGO_VERSION=4.6.0-beta.3 export INSTALLER_IMAGE=${IMAGE_REPOSITORY}/deployer:${PGO_VERSION} export OPERATOR_IMAGE=${IMAGE_REPOSITORY}:${PGO_VERSION} export OPERATOR_IMAGE_API=${IMAGE_REPOSITORY}/pgo-apiserver:${PGO_VERSION} diff --git a/installers/gcp-marketplace/values.yaml b/installers/gcp-marketplace/values.yaml index 7bd7a6c903..762bc7e951 100644 --- a/installers/gcp-marketplace/values.yaml +++ b/installers/gcp-marketplace/values.yaml @@ -10,7 +10,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.1-4.6.0-beta.2" +ccp_image_tag: "centos8-13.1-4.6.0-beta.3" create_rbac: "true" db_name: "" db_password_age_days: "0" @@ -32,9 +32,9 @@ pgo_admin_role_name: "pgoadmin" pgo_admin_username: "admin" pgo_client_container_install: "false" pgo_client_install: 'false' -pgo_client_version: "4.6.0-beta.2" +pgo_client_version: "4.6.0-beta.3" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.0-beta.2" +pgo_image_tag: "centos8-4.6.0-beta.3" pgo_installation_name: '${OPERATOR_NAME}' pgo_operator_namespace: '${OPERATOR_NAMESPACE}' scheduler_timeout: "3600" diff --git a/installers/helm/Chart.yaml b/installers/helm/Chart.yaml index 3f03cf2db0..357f56a686 100644 --- a/installers/helm/Chart.yaml +++ b/installers/helm/Chart.yaml @@ -3,7 +3,7 @@ name: postgres-operator description: Crunchy PostgreSQL Operator Helm chart for Kubernetes type: application version: 0.1.0 -appVersion: 4.6.0-beta.2 +appVersion: 4.6.0-beta.3 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/crunchy_logo.png keywords: diff --git a/installers/helm/values.yaml b/installers/helm/values.yaml index bb5ce533cf..ac0c551af2 100644 --- a/installers/helm/values.yaml +++ b/installers/helm/values.yaml @@ -37,7 +37,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.1-4.6.0-beta.2" +ccp_image_tag: "centos8-13.1-4.6.0-beta.3" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -70,14 +70,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.0-beta.2" +pgo_client_version: "4.6.0-beta.3" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos8-4.6.0-beta.2" +pgo_image_tag: "centos8-4.6.0-beta.3" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/kubectl/client-setup.sh b/installers/kubectl/client-setup.sh index e225efa34a..7306dffe42 100755 --- a/installers/kubectl/client-setup.sh +++ b/installers/kubectl/client-setup.sh @@ -14,7 +14,7 @@ # This script should be run after the operator has been deployed PGO_OPERATOR_NAMESPACE="${PGO_OPERATOR_NAMESPACE:-pgo}" PGO_USER_ADMIN="${PGO_USER_ADMIN:-pgouser-admin}" -PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.0-beta.2}" +PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.0-beta.3}" PGO_CLIENT_URL="https://github.com/CrunchyData/postgres-operator/releases/download/${PGO_CLIENT_VERSION}" PGO_CMD="${PGO_CMD-kubectl}" diff --git a/installers/kubectl/postgres-operator-ocp311.yml b/installers/kubectl/postgres-operator-ocp311.yml index 0baa77037d..5436f123da 100644 --- a/installers/kubectl/postgres-operator-ocp311.yml +++ b/installers/kubectl/postgres-operator-ocp311.yml @@ -44,7 +44,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos8-13.1-4.6.0-beta.2" + ccp_image_tag: "centos8-13.1-4.6.0-beta.3" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -77,14 +77,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.0-beta.2" + pgo_client_version: "4.6.0-beta.3" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos8-4.6.0-beta.2" + pgo_image_tag: "centos8-4.6.0-beta.3" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -161,7 +161,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-beta.2 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-beta.3 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/kubectl/postgres-operator.yml b/installers/kubectl/postgres-operator.yml index 3b0764f057..85ddb15e10 100644 --- a/installers/kubectl/postgres-operator.yml +++ b/installers/kubectl/postgres-operator.yml @@ -139,7 +139,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos8-13.1-4.6.0-beta.2" + ccp_image_tag: "centos8-13.1-4.6.0-beta.3" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -172,14 +172,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.0-beta.2" + pgo_client_version: "4.6.0-beta.3" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos8-4.6.0-beta.2" + pgo_image_tag: "centos8-4.6.0-beta.3" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -269,7 +269,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-beta.2 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-beta.3 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/ansible/README.md b/installers/metrics/ansible/README.md index 41f7d7690d..9a93e81f72 100644 --- a/installers/metrics/ansible/README.md +++ b/installers/metrics/ansible/README.md @@ -4,7 +4,7 @@ Crunchy Data

-Latest Release: 4.6.0-beta.2 +Latest Release: 4.6.0-beta.3 ## General diff --git a/installers/metrics/helm/Chart.yaml b/installers/metrics/helm/Chart.yaml index 5a8debf064..8b9828aadf 100644 --- a/installers/metrics/helm/Chart.yaml +++ b/installers/metrics/helm/Chart.yaml @@ -3,6 +3,6 @@ name: postgres-operator-monitoring description: Install for Crunchy PostgreSQL Operator Monitoring type: application version: 0.1.0 -appVersion: 4.6.0-beta.2 +appVersion: 4.6.0-beta.3 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/crunchy_logo.png \ No newline at end of file diff --git a/installers/metrics/helm/helm_template.yaml b/installers/metrics/helm/helm_template.yaml index 3f89c0d2b5..135a132b19 100644 --- a/installers/metrics/helm/helm_template.yaml +++ b/installers/metrics/helm/helm_template.yaml @@ -20,5 +20,5 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.0-beta.2" +pgo_image_tag: "centos8-4.6.0-beta.3" diff --git a/installers/metrics/helm/values.yaml b/installers/metrics/helm/values.yaml index b7f79ed8de..0c5863629a 100644 --- a/installers/metrics/helm/values.yaml +++ b/installers/metrics/helm/values.yaml @@ -20,7 +20,7 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.0-beta.2" +pgo_image_tag: "centos8-4.6.0-beta.3" # ===================== # Configuration Options diff --git a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml index b449881000..c4d13f9b1c 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml @@ -96,7 +96,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-beta.2 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-beta.3 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/kubectl/postgres-operator-metrics.yml b/installers/metrics/kubectl/postgres-operator-metrics.yml index dea5d62e58..0bd8dee095 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics.yml @@ -165,7 +165,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-beta.2 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-beta.3 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/olm/Makefile b/installers/olm/Makefile index d33f040de4..df7dd962c5 100644 --- a/installers/olm/Makefile +++ b/installers/olm/Makefile @@ -11,7 +11,7 @@ OLM_TOOLS ?= registry.localhost:5000/postgres-operator-olm-tools:$(OLM_SDK_VERSI OLM_VERSION ?= 0.15.1 PGO_BASEOS ?= centos8 PGO_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -PGO_VERSION ?= 4.6.0-beta.2 +PGO_VERSION ?= 4.6.0-beta.3 PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) CCP_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(PGO_VERSION) CCP_POSTGIS_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(CCP_POSTGIS_VERSION)-$(PGO_VERSION) diff --git a/pkg/apis/crunchydata.com/v1/doc.go b/pkg/apis/crunchydata.com/v1/doc.go index 4c92f8a983..eb6528dfd3 100644 --- a/pkg/apis/crunchydata.com/v1/doc.go +++ b/pkg/apis/crunchydata.com/v1/doc.go @@ -53,7 +53,7 @@ cluster. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.0-beta.2", + '{"ClientVersion":"4.6.0-beta.3", "Namespace":"pgouser1", "Name":"mycluster", $PGO_APISERVER_URL/clusters @@ -72,7 +72,7 @@ show all of the clusters that are in the given namespace. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.0-beta.2", + '{"ClientVersion":"4.6.0-beta.3", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/showclusters @@ -82,7 +82,7 @@ $PGO_APISERVER_URL/showclusters curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.0-beta.2", + '{"ClientVersion":"4.6.0-beta.3", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/clustersdelete @@ -90,7 +90,7 @@ $PGO_APISERVER_URL/clustersdelete Schemes: http, https BasePath: / - Version: 4.6.0-beta.2 + Version: 4.6.0-beta.3 License: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0 Contact: Crunchy Data https://www.crunchydata.com/ diff --git a/pkg/apiservermsgs/common.go b/pkg/apiservermsgs/common.go index b2fabc8e0d..5cef464f5f 100644 --- a/pkg/apiservermsgs/common.go +++ b/pkg/apiservermsgs/common.go @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -const PGO_VERSION = "4.6.0-beta.2" +const PGO_VERSION = "4.6.0-beta.3" // Ok status const Ok = "ok" diff --git a/redhat/atomic/help.1 b/redhat/atomic/help.1 index afb2703167..2f1de01426 100644 --- a/redhat/atomic/help.1 +++ b/redhat/atomic/help.1 @@ -56,4 +56,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa \fB\fCRelease=\fR .PP -The specific release number of the container. For example, Release="4.6.0-beta.2" +The specific release number of the container. For example, Release="4.6.0-beta.3" diff --git a/redhat/atomic/help.md b/redhat/atomic/help.md index a18c5a8293..7101976e61 100644 --- a/redhat/atomic/help.md +++ b/redhat/atomic/help.md @@ -45,4 +45,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa `Release=` -The specific release number of the container. For example, Release="4.6.0-beta.2" +The specific release number of the container. For example, Release="4.6.0-beta.3" From 9291d889761743e69690bafd1b8cd94de50f59cc Mon Sep 17 00:00:00 2001 From: Joseph Mckulka <16840147+jmckulk@users.noreply.github.com> Date: Thu, 14 Jan 2021 18:27:41 -0500 Subject: [PATCH 149/276] Update perms on deployer working directory The working directory for pgo-deployer (/tmp/.pgo) is now created at build time instead of install time. When building, the directory is created with root:root as user:group meaning the installer does not have permission to create the required subdirectories. This change recursively updates the user and group on /tmp/.pgo so that the installer can make changes. --- build/pgo-deployer/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pgo-deployer/Dockerfile b/build/pgo-deployer/Dockerfile index 2f18411334..79c3516d2c 100644 --- a/build/pgo-deployer/Dockerfile +++ b/build/pgo-deployer/Dockerfile @@ -79,7 +79,7 @@ ENV HOME="/tmp" RUN chmod g=u /etc/passwd RUN chmod g=u /uid_daemon.sh -RUN chown -R 2:2 /tmp/.pgo/metrics +RUN chown -R 2:2 /tmp/.pgo ENTRYPOINT ["/uid_daemon.sh"] From d07b899a504c7e7fe1ecf5634564671831eb5ea1 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 17 Jan 2021 10:40:22 -0500 Subject: [PATCH 150/276] Remove references to `--autofail` flag This has been gone for a bit. Replace with `--enable-autofail` and `--disable-autofail`. Issue: #2214 --- cmd/pgo/cmd/update.go | 6 +++--- docs/content/architecture/disaster-recovery.md | 2 +- docs/content/pgo-client/common-tasks.md | 2 +- docs/content/pgo-client/reference/pgo_update.md | 4 ++-- docs/content/pgo-client/reference/pgo_update_cluster.md | 2 +- internal/apiserver/clusterservice/clusterimpl.go | 2 +- internal/apiserver/clusterservice/clusterservice.go | 4 ++-- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cmd/pgo/cmd/update.go b/cmd/pgo/cmd/update.go index d16efba880..87df8d5150 100644 --- a/cmd/pgo/cmd/update.go +++ b/cmd/pgo/cmd/update.go @@ -196,8 +196,8 @@ var UpdateCmd = &cobra.Command{ Short: "Update a pgouser, pgorole, or cluster", Long: `The update command allows you to update a pgouser, pgorole, or cluster. For example: - pgo update cluster --selector=name=mycluster --autofail=false - pgo update cluster --all --autofail=true + pgo update cluster --selector=name=mycluster --disable-autofail + pgo update cluster --all --enable-autofail pgo update namespace mynamespace pgo update pgbouncer mycluster --rotate-password pgo update pgorole somerole --pgorole-permission="Cat" @@ -240,7 +240,7 @@ var UpdateClusterCmd = &cobra.Command{ Short: "Update a PostgreSQL cluster", Long: `Update a PostgreSQL cluster. For example: - pgo update cluster mycluster --autofail=false + pgo update cluster mycluster --disable-autofail pgo update cluster mycluster myothercluster --disable-autofail pgo update cluster --selector=name=mycluster --disable-autofail pgo update cluster --all --enable-autofail`, diff --git a/docs/content/architecture/disaster-recovery.md b/docs/content/architecture/disaster-recovery.md index e49a1be579..eb2947b702 100644 --- a/docs/content/architecture/disaster-recovery.md +++ b/docs/content/architecture/disaster-recovery.md @@ -195,7 +195,7 @@ to re-enable autofail if you would like your PostgreSQL cluster to be highly-available. You can re-enable autofail with this command: ```shell -pgo update cluster hacluster --autofail=true +pgo update cluster hacluster --enable-autofail ``` ## Scheduling Backups diff --git a/docs/content/pgo-client/common-tasks.md b/docs/content/pgo-client/common-tasks.md index ae0129a86d..5bb672d6eb 100644 --- a/docs/content/pgo-client/common-tasks.md +++ b/docs/content/pgo-client/common-tasks.md @@ -697,7 +697,7 @@ high availability on the PostgreSQL cluster manually. You can re-enable high availability by executing the following command: ``` -pgo update cluster hacluster --autofail=true +pgo update cluster hacluster --enable-autofail ``` ### Logical Backups (`pg_dump` / `pg_dumpall`) diff --git a/docs/content/pgo-client/reference/pgo_update.md b/docs/content/pgo-client/reference/pgo_update.md index 3045234668..d54fe5dcce 100644 --- a/docs/content/pgo-client/reference/pgo_update.md +++ b/docs/content/pgo-client/reference/pgo_update.md @@ -9,8 +9,8 @@ Update a pgouser, pgorole, or cluster The update command allows you to update a pgouser, pgorole, or cluster. For example: - pgo update cluster --selector=name=mycluster --autofail=false - pgo update cluster --all --autofail=true + pgo update cluster --selector=name=mycluster --disable-autofail + pgo update cluster --all --enable-autofail pgo update namespace mynamespace pgo update pgbouncer mycluster --rotate-password pgo update pgorole somerole --pgorole-permission="Cat" diff --git a/docs/content/pgo-client/reference/pgo_update_cluster.md b/docs/content/pgo-client/reference/pgo_update_cluster.md index 78f1a01ff8..ba9b04e683 100644 --- a/docs/content/pgo-client/reference/pgo_update_cluster.md +++ b/docs/content/pgo-client/reference/pgo_update_cluster.md @@ -9,7 +9,7 @@ Update a PostgreSQL cluster Update a PostgreSQL cluster. For example: - pgo update cluster mycluster --autofail=false + pgo update cluster mycluster --disable-autofail pgo update cluster mycluster myothercluster --disable-autofail pgo update cluster --selector=name=mycluster --disable-autofail pgo update cluster --all --enable-autofail diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index 3dfad2b092..9219afcfa9 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -1867,7 +1867,7 @@ func UpdateCluster(request *msgs.UpdateClusterRequest) msgs.UpdateClusterRespons for i := range clusterList.Items { cluster := clusterList.Items[i] - // set autofail=true or false on each pgcluster CRD + // set --enable-autofail / --disable-autofail on each pgcluster CRD // Make the change based on the value of Autofail vis-a-vis UpdateClusterAutofailStatus switch request.Autofail { case msgs.UpdateClusterAutofailEnable: diff --git a/internal/apiserver/clusterservice/clusterservice.go b/internal/apiserver/clusterservice/clusterservice.go index c26e91cfa4..197a7c1315 100644 --- a/internal/apiserver/clusterservice/clusterservice.go +++ b/internal/apiserver/clusterservice/clusterservice.go @@ -303,8 +303,8 @@ func TestClusterHandler(w http.ResponseWriter, r *http.Request) { } // UpdateClusterHandler ... -// pgo update cluster mycluster --autofail=true -// pgo update cluster --selector=env=research --autofail=false +// pgo update cluster mycluster --enable-autofail +// pgo update cluster --selector=env=research --disable-autofail // returns a UpdateClusterResponse func UpdateClusterHandler(w http.ResponseWriter, r *http.Request) { // swagger:operation POST /clustersupdate clusterservice clustersupdate From 3fe830f97c84032c5c5649729202e93a399f3e27 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 17 Jan 2021 10:52:55 -0500 Subject: [PATCH 151/276] Adjust replica creation logic as part of a standby cluster This ensures an explicit call of standby creation after receiving a notification that a stanza for the cluster was successfully created. Co-authored-by: Andrew L'Ecuyer --- internal/controller/job/backresthandler.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/controller/job/backresthandler.go b/internal/controller/job/backresthandler.go index 2359fa11c0..43f6173311 100644 --- a/internal/controller/job/backresthandler.go +++ b/internal/controller/job/backresthandler.go @@ -139,6 +139,9 @@ func (c *Controller) handleBackrestStanzaCreateUpdate(job *apiv1.Job) error { log.Debugf("job Controller: standby cluster %s will now be set to an initialized "+ "status", clusterName) _ = controller.SetClusterInitializedStatus(c.Client, clusterName, namespace) + + // now initialize the creation of any replica + _ = controller.InitializeReplicaCreation(c.Client, clusterName, namespace) return nil } From acff4bcff8c02483099488d748a2c9a0e5429341 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 17 Jan 2021 16:43:19 -0500 Subject: [PATCH 152/276] Bump Helm chart versions This should be handled on each release, regardless of the changes to the chart. Issue: #2213 --- installers/helm/Chart.yaml | 4 ++-- installers/metrics/helm/Chart.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/installers/helm/Chart.yaml b/installers/helm/Chart.yaml index 357f56a686..9224ba438b 100644 --- a/installers/helm/Chart.yaml +++ b/installers/helm/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: postgres-operator description: Crunchy PostgreSQL Operator Helm chart for Kubernetes type: application -version: 0.1.0 +version: 0.2.0 appVersion: 4.6.0-beta.3 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/crunchy_logo.png @@ -13,4 +13,4 @@ keywords: - Postgres - SQL - NoSQL - - RDBMS \ No newline at end of file + - RDBMS diff --git a/installers/metrics/helm/Chart.yaml b/installers/metrics/helm/Chart.yaml index 8b9828aadf..e954f3e465 100644 --- a/installers/metrics/helm/Chart.yaml +++ b/installers/metrics/helm/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: postgres-operator-monitoring description: Install for Crunchy PostgreSQL Operator Monitoring type: application -version: 0.1.0 +version: 0.2.0 appVersion: 4.6.0-beta.3 home: https://github.com/CrunchyData/postgres-operator -icon: https://github.com/CrunchyData/postgres-operator/raw/master/crunchy_logo.png \ No newline at end of file +icon: https://github.com/CrunchyData/postgres-operator/raw/master/crunchy_logo.png From 5c56a341daf63c7336a9e1a8391b727f65e40442 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 17 Jan 2021 21:44:41 -0500 Subject: [PATCH 153/276] Update label validation to match Kubernetes rules The validation rules for certain kinds of keys are now covered, and values are properly validated against what Kubernetes expects. Issue: [ch10197] Issue: #2215 --- internal/apiserver/labelservice/labelimpl.go | 58 +++++++++++++++----- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/internal/apiserver/labelservice/labelimpl.go b/internal/apiserver/labelservice/labelimpl.go index 2fe6883074..062a12323f 100644 --- a/internal/apiserver/labelservice/labelimpl.go +++ b/internal/apiserver/labelservice/labelimpl.go @@ -17,7 +17,7 @@ limitations under the License. import ( "context" - "errors" + "fmt" "strings" "github.com/crunchydata/postgres-operator/internal/apiserver" @@ -53,7 +53,7 @@ func Label(request *msgs.LabelRequest, ns, pgouser string) msgs.LabelResponse { labelsMap, err = validateLabel(request.LabelCmdLabel) if err != nil { resp.Status.Code = msgs.Error - resp.Status.Msg = "labels not formatted correctly" + resp.Status.Msg = err.Error() return resp } @@ -181,29 +181,57 @@ func addLabels(items []crv1.Pgcluster, DryRun bool, LabelCmdLabel string, newLab } } +// validateLabel validates if the input is a valid Kubernetes label +// +// A label is composed of a key and value. +// +// The key can either be a name or have an optional prefix that i +// terminated by a "/", e.g. "prefix/name" +// +// The name must be a valid DNS 1123 value +// THe prefix must be a valid DNS 1123 subdomain +// +// The value can be validated by machinery provided by Kubenretes +// +// Ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ func validateLabel(LabelCmdLabel string) (map[string]string, error) { - var err error - labelMap := make(map[string]string) - userValues := strings.Split(LabelCmdLabel, ",") - for _, v := range userValues { + labelMap := map[string]string{} + + for _, v := range strings.Split(LabelCmdLabel, ",") { pair := strings.Split(v, "=") if len(pair) != 2 { - log.Error("label format incorrect, requires name=value") - return labelMap, errors.New("label format incorrect, requires name=value") + return labelMap, fmt.Errorf("label format incorrect, requires key=value") } - errs := validation.IsDNS1035Label(pair[0]) - if len(errs) > 0 { - return labelMap, errors.New("label format incorrect, requires name=value " + errs[0]) + // first handle the key + keyParts := strings.Split(pair[0], "/") + + switch len(keyParts) { + default: + return labelMap, fmt.Errorf("invalid key for " + v) + case 2: + if errs := validation.IsDNS1123Subdomain(keyParts[0]); len(errs) > 0 { + return labelMap, fmt.Errorf("invalid key for %s: %s", v, strings.Join(errs, ",")) + } + + if errs := validation.IsDNS1123Label(keyParts[1]); len(errs) > 0 { + return labelMap, fmt.Errorf("invalid key for %s: %s", v, strings.Join(errs, ",")) + } + case 1: + if errs := validation.IsDNS1123Label(keyParts[0]); len(errs) > 0 { + return labelMap, fmt.Errorf("invalid key for %s: %s", v, strings.Join(errs, ",")) + } } - errs = validation.IsDNS1035Label(pair[1]) - if len(errs) > 0 { - return labelMap, errors.New("label format incorrect, requires name=value " + errs[0]) + + // handle the value + if errs := validation.IsValidLabelValue(pair[1]); len(errs) > 0 { + return labelMap, fmt.Errorf("invalid value for %s: %s", v, strings.Join(errs, ",")) } labelMap[pair[0]] = pair[1] } - return labelMap, err + + return labelMap, nil } // DeleteLabel ... From 003b7e8abd99bf6b3b7d75511c846e085bd5776e Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 18 Jan 2021 10:23:59 -0500 Subject: [PATCH 154/276] Refactor of label validation function This moves the function to a common area in the API code and adds some much needed testing to it. --- internal/apiserver/common.go | 57 ++++++++++++++++++ internal/apiserver/common_test.go | 63 ++++++++++++++++++++ internal/apiserver/labelservice/labelimpl.go | 60 +------------------ 3 files changed, 122 insertions(+), 58 deletions(-) diff --git a/internal/apiserver/common.go b/internal/apiserver/common.go index 50e0db963a..7711b0d7f0 100644 --- a/internal/apiserver/common.go +++ b/internal/apiserver/common.go @@ -27,6 +27,7 @@ import ( kerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation" ) const ( @@ -44,6 +45,8 @@ var ( // ErrDBContainerNotFound is an error that indicates that a "database" container // could not be found in a specific pod ErrDBContainerNotFound = errors.New("\"database\" container not found in pod") + // ErrLabelInvalid indicates that a label is invalid + ErrLabelInvalid = errors.New("invalid label") // ErrStandbyNotAllowed contains the error message returned when an API call is not // permitted because it involves a cluster that is in standby mode ErrStandbyNotAllowed = errors.New("Action not permitted because standby mode is enabled") @@ -127,6 +130,60 @@ func ValidateBackrestStorageTypeForCommand(cluster *crv1.Pgcluster, storageTypeS return nil } +// ValidateLabel is derived from a legacy method and validates if the input is a +// valid Kubernetes label. +// +// A label is composed of a key and value. +// +// The key can either be a name or have an optional prefix that i +// terminated by a "/", e.g. "prefix/name" +// +// The name must be a valid DNS 1123 value +// THe prefix must be a valid DNS 1123 subdomain +// +// The value can be validated by machinery provided by Kubenretes +// +// Ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +func ValidateLabel(labelStr string) (map[string]string, error) { + labelMap := map[string]string{} + + for _, v := range strings.Split(labelStr, ",") { + pair := strings.Split(v, "=") + if len(pair) != 2 { + return labelMap, fmt.Errorf("%w: label format incorrect, requires key=value", ErrLabelInvalid) + } + + // first handle the key + keyParts := strings.Split(pair[0], "/") + + switch len(keyParts) { + default: + return labelMap, fmt.Errorf("%w: invalid key for "+v, ErrLabelInvalid) + case 2: + if errs := validation.IsDNS1123Subdomain(keyParts[0]); len(errs) > 0 { + return labelMap, fmt.Errorf("%w: invalid key for %s: %s", ErrLabelInvalid, v, strings.Join(errs, ",")) + } + + if errs := validation.IsDNS1123Label(keyParts[1]); len(errs) > 0 { + return labelMap, fmt.Errorf("%w: invalid key for %s: %s", ErrLabelInvalid, v, strings.Join(errs, ",")) + } + case 1: + if errs := validation.IsDNS1123Label(keyParts[0]); len(errs) > 0 { + return labelMap, fmt.Errorf("%w: invalid key for %s: %s", ErrLabelInvalid, v, strings.Join(errs, ",")) + } + } + + // handle the value + if errs := validation.IsValidLabelValue(pair[1]); len(errs) > 0 { + return labelMap, fmt.Errorf("%w: invalid value for %s: %s", ErrLabelInvalid, v, strings.Join(errs, ",")) + } + + labelMap[pair[0]] = pair[1] + } + + return labelMap, nil +} + // ValidateResourceRequestLimit validates that a Kubernetes Requests/Limit pair // is valid, both by validating the values are valid quantity values, and then // by checking that the limit >= request. This also needs to check against the diff --git a/internal/apiserver/common_test.go b/internal/apiserver/common_test.go index 9f11dc4e49..13507313b8 100644 --- a/internal/apiserver/common_test.go +++ b/internal/apiserver/common_test.go @@ -16,6 +16,9 @@ limitations under the License. */ import ( + "errors" + "fmt" + "strings" "testing" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" @@ -164,6 +167,66 @@ func TestValidateBackrestStorageTypeForCommand(t *testing.T) { }) } +func TestValidateLabel(t *testing.T) { + t.Run("valid", func(t *testing.T) { + inputs := []map[string]string{ + map[string]string{"key": "value"}, + map[string]string{"example.com/key": "value"}, + map[string]string{"key1": "value1", "key2": "value2"}, + } + + for _, input := range inputs { + labelStr := "" + + for k, v := range input { + labelStr += fmt.Sprintf("%s=%s,", k, v) + } + + labelStr = strings.Trim(labelStr, ",") + + t.Run(labelStr, func(*testing.T) { + labels, err := ValidateLabel(labelStr) + + if err != nil { + t.Fatalf("expected no error, got: %s", err.Error()) + } + + for k := range labels { + if v, ok := input[k]; !(ok || v == labels[k]) { + t.Fatalf("label values do not matched (%s vs. %s)", input[k], labels[k]) + } + } + }) + } + }) + + t.Run("invalid", func(t *testing.T) { + inputs := []string{ + "key", + "key=value=value", + "key=value,", + "b@d=value", + "b@d-prefix/key=value", + "really/bad/prefix/key=value", + "key=v\\alue", + } + + for _, input := range inputs { + t.Run(input, func(t *testing.T) { + _, err := ValidateLabel(input) + + if err == nil { + t.Fatalf("expected an invalid input error.") + } + + if !errors.Is(err, ErrLabelInvalid) { + t.Fatalf("expected an ErrLabelInvalid error.") + } + }) + } + }) +} + func TestValidateResourceRequestLimit(t *testing.T) { t.Run("valid", func(t *testing.T) { resources := []struct{ request, limit, defaultRequest string }{ diff --git a/internal/apiserver/labelservice/labelimpl.go b/internal/apiserver/labelservice/labelimpl.go index 062a12323f..d2ee42ccf6 100644 --- a/internal/apiserver/labelservice/labelimpl.go +++ b/internal/apiserver/labelservice/labelimpl.go @@ -17,8 +17,6 @@ limitations under the License. import ( "context" - "fmt" - "strings" "github.com/crunchydata/postgres-operator/internal/apiserver" "github.com/crunchydata/postgres-operator/internal/config" @@ -29,7 +27,6 @@ import ( log "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/validation" ) // Label ... 2 forms ... @@ -50,7 +47,7 @@ func Label(request *msgs.LabelRequest, ns, pgouser string) msgs.LabelResponse { return resp } - labelsMap, err = validateLabel(request.LabelCmdLabel) + labelsMap, err = apiserver.ValidateLabel(request.LabelCmdLabel) if err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() @@ -181,59 +178,6 @@ func addLabels(items []crv1.Pgcluster, DryRun bool, LabelCmdLabel string, newLab } } -// validateLabel validates if the input is a valid Kubernetes label -// -// A label is composed of a key and value. -// -// The key can either be a name or have an optional prefix that i -// terminated by a "/", e.g. "prefix/name" -// -// The name must be a valid DNS 1123 value -// THe prefix must be a valid DNS 1123 subdomain -// -// The value can be validated by machinery provided by Kubenretes -// -// Ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ -func validateLabel(LabelCmdLabel string) (map[string]string, error) { - labelMap := map[string]string{} - - for _, v := range strings.Split(LabelCmdLabel, ",") { - pair := strings.Split(v, "=") - if len(pair) != 2 { - return labelMap, fmt.Errorf("label format incorrect, requires key=value") - } - - // first handle the key - keyParts := strings.Split(pair[0], "/") - - switch len(keyParts) { - default: - return labelMap, fmt.Errorf("invalid key for " + v) - case 2: - if errs := validation.IsDNS1123Subdomain(keyParts[0]); len(errs) > 0 { - return labelMap, fmt.Errorf("invalid key for %s: %s", v, strings.Join(errs, ",")) - } - - if errs := validation.IsDNS1123Label(keyParts[1]); len(errs) > 0 { - return labelMap, fmt.Errorf("invalid key for %s: %s", v, strings.Join(errs, ",")) - } - case 1: - if errs := validation.IsDNS1123Label(keyParts[0]); len(errs) > 0 { - return labelMap, fmt.Errorf("invalid key for %s: %s", v, strings.Join(errs, ",")) - } - } - - // handle the value - if errs := validation.IsValidLabelValue(pair[1]); len(errs) > 0 { - return labelMap, fmt.Errorf("invalid value for %s: %s", v, strings.Join(errs, ",")) - } - - labelMap[pair[0]] = pair[1] - } - - return labelMap, nil -} - // DeleteLabel ... // pgo delete label mycluster yourcluster --label=env=prod // pgo delete label --label=env=prod --selector=group=somegroup @@ -252,7 +196,7 @@ func DeleteLabel(request *msgs.DeleteLabelRequest, ns string) msgs.LabelResponse return resp } - labelsMap, err = validateLabel(request.LabelCmdLabel) + labelsMap, err = apiserver.ValidateLabel(request.LabelCmdLabel) if err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = "labels not formatted correctly" From c3a21f8c379a2a7e6f7d1001555cb85dc7860db6 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 18 Jan 2021 10:31:28 -0500 Subject: [PATCH 155/276] Unify label validation strategy across API functions The `pgo create cluster` command was not using the same label validation as the other label commands were using. Issue: [ch10201] --- .../apiserver/clusterservice/clusterimpl.go | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index 9219afcfa9..92ff1f93de 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -576,18 +576,12 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. return resp } - userLabelsMap := make(map[string]string) - if request.UserLabels != "" { - labels := strings.Split(request.UserLabels, ",") - for _, v := range labels { - p := strings.Split(v, "=") - if len(p) < 2 { - resp.Status.Code = msgs.Error - resp.Status.Msg = "invalid labels format" - return resp - } - userLabelsMap[p[0]] = p[1] - } + userLabelsMap, err := apiserver.ValidateLabel(request.UserLabels) + + if err != nil { + resp.Status.Code = msgs.Error + resp.Status.Msg = err.Error() + return resp } // validate any parameters provided to bootstrap the cluster from an existing data source From 630cec5bee21584b3315a188f4f50c567db417d6 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 18 Jan 2021 11:33:17 -0500 Subject: [PATCH 156/276] Update labeling interface to be consistent with others This moves the labeling interface to match that of similar ones, e.g. "annotations." - `pgo create cluster`, `pgo label`, and `pgo delete label` all now have a single `--label` flag. `--label` can be specified multiple times. - The API call itself takes a mapping of key/value pairs. - The API endpoint parameter for `pgo label` and `pgo delete label` is now called `Labels` Issue: [ch10202] --- cmd/pgo/cmd/cluster.go | 2 +- cmd/pgo/cmd/common.go | 29 +++++++++ cmd/pgo/cmd/create.go | 5 +- cmd/pgo/cmd/delete.go | 5 +- cmd/pgo/cmd/label.go | 23 +++---- .../reference/pgo_create_cluster.md | 4 +- .../pgo-client/reference/pgo_delete_label.md | 4 +- .../content/pgo-client/reference/pgo_label.md | 4 +- .../apiserver/clusterservice/clusterimpl.go | 13 ++-- internal/apiserver/common.go | 57 ----------------- internal/apiserver/common_test.go | 63 ------------------- internal/apiserver/labelservice/labelimpl.go | 42 +++---------- internal/util/cluster.go | 57 +++++++++++++++-- internal/util/cluster_test.go | 44 +++++++++++++ pkg/apiservermsgs/clustermsgs.go | 2 +- pkg/apiservermsgs/labelmsgs.go | 4 +- pkg/events/eventtype.go | 17 ----- 17 files changed, 166 insertions(+), 209 deletions(-) diff --git a/cmd/pgo/cmd/cluster.go b/cmd/pgo/cmd/cluster.go index 848fd0e995..7d95f782ff 100644 --- a/cmd/pgo/cmd/cluster.go +++ b/cmd/pgo/cmd/cluster.go @@ -267,7 +267,7 @@ func createCluster(args []string, ns string, createClusterCmd *cobra.Command) { r.PasswordReplication = PasswordReplication r.Password = Password r.SecretFrom = SecretFrom - r.UserLabels = UserLabels + r.UserLabels = getLabels(UserLabels) r.Policies = PoliciesFlag r.CCPImageTag = CCPImageTag r.CCPImage = CCPImage diff --git a/cmd/pgo/cmd/common.go b/cmd/pgo/cmd/common.go index 087326b57d..66b1f4bd51 100644 --- a/cmd/pgo/cmd/common.go +++ b/cmd/pgo/cmd/common.go @@ -20,7 +20,9 @@ import ( "fmt" "os" "reflect" + "strings" + operatorutil "github.com/crunchydata/postgres-operator/internal/util" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" ) @@ -85,6 +87,33 @@ func getHeaderLength(value interface{}, fieldName string) int { return len(field.String()) } +// getLabels determines if the provided labels are in the correct format, +// and if so, will return them in the appropriate map. +// +// If not, we will abort. +func getLabels(labels []string) map[string]string { + clusterLabels := map[string]string{} + + for _, label := range labels { + parts := strings.Split(label, "=") + + if len(parts) != 2 { + fmt.Printf("invalid label: found %q, should be \"key=value\"\n", label) + os.Exit(1) + } + + clusterLabels[parts[0]] = parts[1] + } + + // perform a validation that can save us a round trip to the server + if err := operatorutil.ValidateLabels(clusterLabels); err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + + return clusterLabels +} + // getMaxLength returns the maxLength of the strings of a particular value in // the struct. Increases the max length by 1 to include a buffer func getMaxLength(results []interface{}, title, fieldName string) int { diff --git a/cmd/pgo/cmd/create.go b/cmd/pgo/cmd/create.go index d1cd3eaa14..aabfa84e74 100644 --- a/cmd/pgo/cmd/create.go +++ b/cmd/pgo/cmd/create.go @@ -39,7 +39,7 @@ var ( Password string SecretFrom string PoliciesFlag, PolicyFile string - UserLabels string + UserLabels []string Tablespaces []string ServiceType string ServiceTypePgBouncer string @@ -392,7 +392,8 @@ func init() { createClusterCmd.Flags().StringVarP(&CustomConfig, "custom-config", "", "", "The name of a configMap that holds custom PostgreSQL configuration files used to override defaults.") createClusterCmd.Flags().StringVarP(&Database, "database", "d", "", "If specified, sets the name of the initial database that is created for the user. Defaults to the value set in the PostgreSQL Operator configuration, or if that is not present, the name of the cluster") createClusterCmd.Flags().BoolVarP(&DisableAutofailFlag, "disable-autofail", "", false, "Disables autofail capabitilies in the cluster following cluster initialization.") - createClusterCmd.Flags().StringVarP(&UserLabels, "labels", "l", "", "The labels to apply to this cluster.") + createClusterCmd.Flags().StringSliceVar(&UserLabels, "label", []string{}, "Add labels to apply to the PostgreSQL cluster, "+ + "e.g. \"key=value\", \"prefix/key=value\". Can specify flag multiple times.") createClusterCmd.Flags().StringVar(&MemoryRequest, "memory", "", "Set the amount of RAM to request, e.g. "+ "1GiB. Overrides the default server value.") createClusterCmd.Flags().StringVar(&MemoryLimit, "memory-limit", "", "Set the amount of RAM to limit, e.g. "+ diff --git a/cmd/pgo/cmd/delete.go b/cmd/pgo/cmd/delete.go index 511204ac8e..8397690492 100644 --- a/cmd/pgo/cmd/delete.go +++ b/cmd/pgo/cmd/delete.go @@ -170,8 +170,9 @@ func init() { deleteCmd.AddCommand(deleteLabelCmd) // pgo delete label --label // the label to be deleted - deleteLabelCmd.Flags().StringVar(&LabelCmdLabel, "label", "", - "The label to delete for any selected or specified clusters.") + deleteLabelCmd.Flags().StringSliceVar(&UserLabels, "label", []string{}, "Delete "+ + "labels to apply to the PostgreSQL cluster, "+"e.g. \"key=value\", \"prefix/key=value\". "+ + "Can specify flag multiple times.") // "pgo delete label --selector" // "pgo delete label -s" // the selector flag that filters which clusters to delete the cluster diff --git a/cmd/pgo/cmd/label.go b/cmd/pgo/cmd/label.go index 70b18061e5..fcda3b7c82 100644 --- a/cmd/pgo/cmd/label.go +++ b/cmd/pgo/cmd/label.go @@ -26,9 +26,7 @@ import ( ) var ( - LabelCmdLabel string - LabelMap map[string]string - DeleteLabel bool + DeleteLabel bool ) var labelCmd = &cobra.Command{ @@ -47,22 +45,25 @@ var labelCmd = &cobra.Command{ log.Debug("label called") if len(args) == 0 && Selector == "" { fmt.Println("Error: A selector or list of clusters is required to label a policy.") - return + os.Exit(1) } - if LabelCmdLabel == "" { + + if len(UserLabels) == 0 { fmt.Println("Error: You must specify the label to apply.") - } else { - labelClusters(args, Namespace) + os.Exit(1) } + + labelClusters(args, Namespace) }, } func init() { RootCmd.AddCommand(labelCmd) + labelCmd.Flags().BoolVar(&DryRun, "dry-run", false, "Shows the clusters that the label would be applied to, without labelling them.") + labelCmd.Flags().StringSliceVar(&UserLabels, "label", []string{}, "Add labels to apply to the PostgreSQL cluster, "+ + "e.g. \"key=value\", \"prefix/key=value\". Can specify flag multiple times.") labelCmd.Flags().StringVarP(&Selector, "selector", "s", "", "The selector to use for cluster filtering.") - labelCmd.Flags().StringVarP(&LabelCmdLabel, "label", "", "", "The new label to apply for any selected or specified clusters.") - labelCmd.Flags().BoolVarP(&DryRun, "dry-run", "", false, "Shows the clusters that the label would be applied to, without labelling them.") } func labelClusters(clusters []string, ns string) { @@ -78,7 +79,7 @@ func labelClusters(clusters []string, ns string) { r.Namespace = ns r.Selector = Selector r.DryRun = DryRun - r.LabelCmdLabel = LabelCmdLabel + r.Labels = getLabels(UserLabels) r.DeleteLabel = DeleteLabel r.ClientVersion = msgs.PGO_VERSION @@ -111,7 +112,7 @@ func deleteLabel(args []string, ns string) { req.Selector = Selector req.Namespace = ns req.Args = args - req.LabelCmdLabel = LabelCmdLabel + req.Labels = getLabels(UserLabels) req.ClientVersion = msgs.PGO_VERSION response, err := api.DeleteLabel(httpclient, &SessionCredentials, &req) diff --git a/docs/content/pgo-client/reference/pgo_create_cluster.md b/docs/content/pgo-client/reference/pgo_create_cluster.md index 9993e29127..4fa7532a5b 100644 --- a/docs/content/pgo-client/reference/pgo_create_cluster.md +++ b/docs/content/pgo-client/reference/pgo_create_cluster.md @@ -45,7 +45,7 @@ pgo create cluster [flags] --exporter-memory string Set the amount of memory to request for the Crunchy Postgres Exporter sidecar container. Defaults to server value (24Mi). --exporter-memory-limit string Set the amount of memory to limit for the Crunchy Postgres Exporter sidecar container. -h, --help help for cluster - -l, --labels string The labels to apply to this cluster. + --label strings Add labels to apply to the PostgreSQL cluster, e.g. "key=value", "prefix/key=value". Can specify flag multiple times. --memory string Set the amount of RAM to request, e.g. 1GiB. Overrides the default server value. --memory-limit string Set the amount of RAM to limit, e.g. 1GiB. --metrics Adds the crunchy-postgres-exporter container to the database pod. @@ -136,4 +136,4 @@ pgo create cluster [flags] * [pgo create](/pgo-client/reference/pgo_create/) - Create a Postgres Operator resource -###### Auto generated by spf13/cobra on 14-Jan-2021 +###### Auto generated by spf13/cobra on 18-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_delete_label.md b/docs/content/pgo-client/reference/pgo_delete_label.md index 2e8efeb5c3..dbd22e4bf6 100644 --- a/docs/content/pgo-client/reference/pgo_delete_label.md +++ b/docs/content/pgo-client/reference/pgo_delete_label.md @@ -21,7 +21,7 @@ pgo delete label [flags] ``` -h, --help help for label - --label string The label to delete for any selected or specified clusters. + --label strings Delete labels to apply to the PostgreSQL cluster, e.g. "key=value", "prefix/key=value". Can specify flag multiple times. -s, --selector string The selector to use for cluster filtering. ``` @@ -42,4 +42,4 @@ pgo delete label [flags] * [pgo delete](/pgo-client/reference/pgo_delete/) - Delete an Operator resource -###### Auto generated by spf13/cobra on 14-Jan-2021 +###### Auto generated by spf13/cobra on 18-Jan-2021 diff --git a/docs/content/pgo-client/reference/pgo_label.md b/docs/content/pgo-client/reference/pgo_label.md index abcb3e0115..143b217ec6 100644 --- a/docs/content/pgo-client/reference/pgo_label.md +++ b/docs/content/pgo-client/reference/pgo_label.md @@ -23,7 +23,7 @@ pgo label [flags] ``` --dry-run Shows the clusters that the label would be applied to, without labelling them. -h, --help help for label - --label string The new label to apply for any selected or specified clusters. + --label strings Add labels to apply to the PostgreSQL cluster, e.g. "key=value", "prefix/key=value". Can specify flag multiple times. -s, --selector string The selector to use for cluster filtering. ``` @@ -44,4 +44,4 @@ pgo label [flags] * [pgo](/pgo-client/reference/pgo/) - The pgo command line interface. -###### Auto generated by spf13/cobra on 14-Jan-2021 +###### Auto generated by spf13/cobra on 18-Jan-2021 diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index 92ff1f93de..18999d66eb 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -576,9 +576,7 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. return resp } - userLabelsMap, err := apiserver.ValidateLabel(request.UserLabels) - - if err != nil { + if err := util.ValidateLabels(request.UserLabels); err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() return resp @@ -753,9 +751,6 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. return resp } - log.Debug("userLabelsMap") - log.Debugf("%v", userLabelsMap) - if request.StorageConfig != "" && !apiserver.IsValidStorageName(request.StorageConfig) { resp.Status.Code = msgs.Error resp.Status.Msg = fmt.Sprintf("%q storage config was not found", request.StorageConfig) @@ -857,7 +852,7 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. } // Create an instance of our CRD - newInstance := getClusterParams(request, clusterName, userLabelsMap, ns) + newInstance := getClusterParams(request, clusterName, ns) newInstance.ObjectMeta.Labels[config.LABEL_PGOUSER] = pgouser newInstance.Spec.BackrestStorageTypes = backrestStorageTypes @@ -1066,7 +1061,7 @@ func validateConfigPolicies(clusterName, PoliciesFlag, ns string) error { return err } -func getClusterParams(request *msgs.CreateClusterRequest, name string, userLabelsMap map[string]string, ns string) *crv1.Pgcluster { +func getClusterParams(request *msgs.CreateClusterRequest, name string, ns string) *crv1.Pgcluster { spec := crv1.PgclusterSpec{ Annotations: crv1.ClusterAnnotations{ Backrest: map[string]string{}, @@ -1363,7 +1358,7 @@ func getClusterParams(request *msgs.CreateClusterRequest, name string, userLabel spec.ServiceType = request.ServiceType - spec.UserLabels = userLabelsMap + spec.UserLabels = request.UserLabels spec.UserLabels[config.LABEL_PGO_VERSION] = msgs.PGO_VERSION // override any values from config file diff --git a/internal/apiserver/common.go b/internal/apiserver/common.go index 7711b0d7f0..50e0db963a 100644 --- a/internal/apiserver/common.go +++ b/internal/apiserver/common.go @@ -27,7 +27,6 @@ import ( kerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/validation" ) const ( @@ -45,8 +44,6 @@ var ( // ErrDBContainerNotFound is an error that indicates that a "database" container // could not be found in a specific pod ErrDBContainerNotFound = errors.New("\"database\" container not found in pod") - // ErrLabelInvalid indicates that a label is invalid - ErrLabelInvalid = errors.New("invalid label") // ErrStandbyNotAllowed contains the error message returned when an API call is not // permitted because it involves a cluster that is in standby mode ErrStandbyNotAllowed = errors.New("Action not permitted because standby mode is enabled") @@ -130,60 +127,6 @@ func ValidateBackrestStorageTypeForCommand(cluster *crv1.Pgcluster, storageTypeS return nil } -// ValidateLabel is derived from a legacy method and validates if the input is a -// valid Kubernetes label. -// -// A label is composed of a key and value. -// -// The key can either be a name or have an optional prefix that i -// terminated by a "/", e.g. "prefix/name" -// -// The name must be a valid DNS 1123 value -// THe prefix must be a valid DNS 1123 subdomain -// -// The value can be validated by machinery provided by Kubenretes -// -// Ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ -func ValidateLabel(labelStr string) (map[string]string, error) { - labelMap := map[string]string{} - - for _, v := range strings.Split(labelStr, ",") { - pair := strings.Split(v, "=") - if len(pair) != 2 { - return labelMap, fmt.Errorf("%w: label format incorrect, requires key=value", ErrLabelInvalid) - } - - // first handle the key - keyParts := strings.Split(pair[0], "/") - - switch len(keyParts) { - default: - return labelMap, fmt.Errorf("%w: invalid key for "+v, ErrLabelInvalid) - case 2: - if errs := validation.IsDNS1123Subdomain(keyParts[0]); len(errs) > 0 { - return labelMap, fmt.Errorf("%w: invalid key for %s: %s", ErrLabelInvalid, v, strings.Join(errs, ",")) - } - - if errs := validation.IsDNS1123Label(keyParts[1]); len(errs) > 0 { - return labelMap, fmt.Errorf("%w: invalid key for %s: %s", ErrLabelInvalid, v, strings.Join(errs, ",")) - } - case 1: - if errs := validation.IsDNS1123Label(keyParts[0]); len(errs) > 0 { - return labelMap, fmt.Errorf("%w: invalid key for %s: %s", ErrLabelInvalid, v, strings.Join(errs, ",")) - } - } - - // handle the value - if errs := validation.IsValidLabelValue(pair[1]); len(errs) > 0 { - return labelMap, fmt.Errorf("%w: invalid value for %s: %s", ErrLabelInvalid, v, strings.Join(errs, ",")) - } - - labelMap[pair[0]] = pair[1] - } - - return labelMap, nil -} - // ValidateResourceRequestLimit validates that a Kubernetes Requests/Limit pair // is valid, both by validating the values are valid quantity values, and then // by checking that the limit >= request. This also needs to check against the diff --git a/internal/apiserver/common_test.go b/internal/apiserver/common_test.go index 13507313b8..9f11dc4e49 100644 --- a/internal/apiserver/common_test.go +++ b/internal/apiserver/common_test.go @@ -16,9 +16,6 @@ limitations under the License. */ import ( - "errors" - "fmt" - "strings" "testing" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" @@ -167,66 +164,6 @@ func TestValidateBackrestStorageTypeForCommand(t *testing.T) { }) } -func TestValidateLabel(t *testing.T) { - t.Run("valid", func(t *testing.T) { - inputs := []map[string]string{ - map[string]string{"key": "value"}, - map[string]string{"example.com/key": "value"}, - map[string]string{"key1": "value1", "key2": "value2"}, - } - - for _, input := range inputs { - labelStr := "" - - for k, v := range input { - labelStr += fmt.Sprintf("%s=%s,", k, v) - } - - labelStr = strings.Trim(labelStr, ",") - - t.Run(labelStr, func(*testing.T) { - labels, err := ValidateLabel(labelStr) - - if err != nil { - t.Fatalf("expected no error, got: %s", err.Error()) - } - - for k := range labels { - if v, ok := input[k]; !(ok || v == labels[k]) { - t.Fatalf("label values do not matched (%s vs. %s)", input[k], labels[k]) - } - } - }) - } - }) - - t.Run("invalid", func(t *testing.T) { - inputs := []string{ - "key", - "key=value=value", - "key=value,", - "b@d=value", - "b@d-prefix/key=value", - "really/bad/prefix/key=value", - "key=v\\alue", - } - - for _, input := range inputs { - t.Run(input, func(t *testing.T) { - _, err := ValidateLabel(input) - - if err == nil { - t.Fatalf("expected an invalid input error.") - } - - if !errors.Is(err, ErrLabelInvalid) { - t.Fatalf("expected an ErrLabelInvalid error.") - } - }) - } - }) -} - func TestValidateResourceRequestLimit(t *testing.T) { t.Run("valid", func(t *testing.T) { resources := []struct{ request, limit, defaultRequest string }{ diff --git a/internal/apiserver/labelservice/labelimpl.go b/internal/apiserver/labelservice/labelimpl.go index d2ee42ccf6..ffe975311b 100644 --- a/internal/apiserver/labelservice/labelimpl.go +++ b/internal/apiserver/labelservice/labelimpl.go @@ -21,9 +21,9 @@ import ( "github.com/crunchydata/postgres-operator/internal/apiserver" "github.com/crunchydata/postgres-operator/internal/config" "github.com/crunchydata/postgres-operator/internal/kubeapi" + "github.com/crunchydata/postgres-operator/internal/util" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" - "github.com/crunchydata/postgres-operator/pkg/events" log "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -34,8 +34,7 @@ import ( // pgo label --label=env=prod --selector=name=mycluster func Label(request *msgs.LabelRequest, ns, pgouser string) msgs.LabelResponse { ctx := context.TODO() - var err error - var labelsMap map[string]string + resp := msgs.LabelResponse{} resp.Status.Code = msgs.Ok resp.Status.Msg = "" @@ -47,8 +46,7 @@ func Label(request *msgs.LabelRequest, ns, pgouser string) msgs.LabelResponse { return resp } - labelsMap, err = apiserver.ValidateLabel(request.LabelCmdLabel) - if err != nil { + if err := util.ValidateLabels(request.Labels); err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() return resp @@ -106,12 +104,12 @@ func Label(request *msgs.LabelRequest, ns, pgouser string) msgs.LabelResponse { resp.Results = append(resp.Results, c.Spec.Name) } - addLabels(clusterList.Items, request.DryRun, request.LabelCmdLabel, labelsMap, ns, pgouser) + addLabels(clusterList.Items, request.DryRun, request.Labels, ns) return resp } -func addLabels(items []crv1.Pgcluster, DryRun bool, LabelCmdLabel string, newLabels map[string]string, ns, pgouser string) { +func addLabels(items []crv1.Pgcluster, DryRun bool, newLabels map[string]string, ns string) { ctx := context.TODO() patchBytes, err := kubeapi.NewMergePatch().Add("metadata", "labels")(newLabels).Bytes() if err != nil { @@ -129,27 +127,6 @@ func addLabels(items []crv1.Pgcluster, DryRun bool, LabelCmdLabel string, newLab if err != nil { log.Error(err.Error()) } - - // publish event for create label - topics := make([]string, 1) - topics[0] = events.EventTopicCluster - - f := events.EventCreateLabelFormat{ - EventHeader: events.EventHeader{ - Namespace: ns, - Username: pgouser, - Topic: topics, - EventType: events.EventCreateLabel, - }, - Clustername: items[i].Spec.Name, - Label: LabelCmdLabel, - } - - err = events.Publish(f) - if err != nil { - log.Error(err.Error()) - } - } } @@ -183,8 +160,7 @@ func addLabels(items []crv1.Pgcluster, DryRun bool, LabelCmdLabel string, newLab // pgo delete label --label=env=prod --selector=group=somegroup func DeleteLabel(request *msgs.DeleteLabelRequest, ns string) msgs.LabelResponse { ctx := context.TODO() - var err error - var labelsMap map[string]string + resp := msgs.LabelResponse{} resp.Status.Code = msgs.Ok resp.Status.Msg = "" @@ -196,8 +172,7 @@ func DeleteLabel(request *msgs.DeleteLabelRequest, ns string) msgs.LabelResponse return resp } - labelsMap, err = apiserver.ValidateLabel(request.LabelCmdLabel) - if err != nil { + if err := util.ValidateLabels(request.Labels); err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = "labels not formatted correctly" return resp @@ -253,8 +228,7 @@ func DeleteLabel(request *msgs.DeleteLabelRequest, ns string) msgs.LabelResponse resp.Results = append(resp.Results, "deleting label from "+c.Spec.Name) } - err = deleteLabels(clusterList.Items, labelsMap, ns) - if err != nil { + if err := deleteLabels(clusterList.Items, request.Labels, ns); err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() return resp diff --git a/internal/util/cluster.go b/internal/util/cluster.go index 2349cedbb0..25d85c4609 100644 --- a/internal/util/cluster.go +++ b/internal/util/cluster.go @@ -31,6 +31,7 @@ import ( v1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) @@ -105,10 +106,14 @@ const ( sqlSetPasswordDefault = `ALTER ROLE %s PASSWORD %s;` ) -// ErrMissingConfigAnnotation represents an error thrown when the 'config' annotation is found -// to be missing from the 'config' configMap created to store cluster-wide configuration -var ErrMissingConfigAnnotation error = errors.New("'config' annotation missing from cluster " + - "configutation") +var ( + // ErrLabelInvalid indicates that a label is invalid + ErrLabelInvalid = errors.New("invalid label") + // ErrMissingConfigAnnotation represents an error thrown when the 'config' annotation is found + // to be missing from the 'config' configMap created to store cluster-wide configuration + ErrMissingConfigAnnotation error = errors.New("'config' annotation missing from cluster " + + "configutation") +) // CmdStopPostgreSQL is the command used to stop a PostgreSQL instance, which // uses the "fast" shutdown mode. This needs a data directory appended to it @@ -461,3 +466,47 @@ func StopPostgreSQLInstance(clientset kubernetes.Interface, restconfig *rest.Con return nil } + +// ValidateLabels validates if the input is a valid Kubernetes label. +// +// A label is composed of a key and value. +// +// The key can either be a name or have an optional prefix that i +// terminated by a "/", e.g. "prefix/name" +// +// The name must be a valid DNS 1123 value +// THe prefix must be a valid DNS 1123 subdomain +// +// The value can be validated by machinery provided by Kubenretes +// +// Ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +func ValidateLabels(labels map[string]string) error { + for k, v := range labels { + // first handle the key + keyParts := strings.Split(k, "/") + + switch len(keyParts) { + default: + return fmt.Errorf("%w: invalid key for "+v, ErrLabelInvalid) + case 2: + if errs := validation.IsDNS1123Subdomain(keyParts[0]); len(errs) > 0 { + return fmt.Errorf("%w: invalid key %s: %s", ErrLabelInvalid, k, strings.Join(errs, ",")) + } + + if errs := validation.IsDNS1123Label(keyParts[1]); len(errs) > 0 { + return fmt.Errorf("%w: invalid key %s: %s", ErrLabelInvalid, k, strings.Join(errs, ",")) + } + case 1: + if errs := validation.IsDNS1123Label(keyParts[0]); len(errs) > 0 { + return fmt.Errorf("%w: invalid key %s: %s", ErrLabelInvalid, k, strings.Join(errs, ",")) + } + } + + // handle the value + if errs := validation.IsValidLabelValue(v); len(errs) > 0 { + return fmt.Errorf("%w: invalid value %s: %s", ErrLabelInvalid, v, strings.Join(errs, ",")) + } + } + + return nil +} diff --git a/internal/util/cluster_test.go b/internal/util/cluster_test.go index 6bb8ea472a..98d3a0a1f0 100644 --- a/internal/util/cluster_test.go +++ b/internal/util/cluster_test.go @@ -16,11 +16,14 @@ limitations under the License. */ import ( + "errors" "reflect" "testing" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/labels" ) func TestGenerateNodeAffinity(t *testing.T) { @@ -114,3 +117,44 @@ func TestGenerateNodeAffinity(t *testing.T) { } }) } + +func TestValidateLabels(t *testing.T) { + t.Run("valid", func(t *testing.T) { + inputs := []map[string]string{ + {"key": "value"}, + {"example.com/key": "value"}, + {"key1": "value1", "key2": "value2"}, + } + + for _, input := range inputs { + t.Run(labels.FormatLabels(input), func(*testing.T) { + err := ValidateLabels(input) + + if err != nil { + t.Fatalf("expected no error, got: %s", err.Error()) + } + }) + } + }) + + t.Run("invalid", func(t *testing.T) { + inputs := []map[string]string{ + {"key=value": "value"}, + {"key": "value", "": ""}, + {"b@d": "value"}, + {"b@d-prefix/key": "value"}, + {"really/bad/prefix/key": "value"}, + {"key": "v\\alue"}, + } + + for _, input := range inputs { + t.Run(labels.FormatLabels(input), func(t *testing.T) { + err := ValidateLabels(input) + + if !errors.Is(err, ErrLabelInvalid) { + t.Fatalf("expected an ErrLabelInvalid error, got %T: %v", err, err) + } + }) + } + }) +} diff --git a/pkg/apiservermsgs/clustermsgs.go b/pkg/apiservermsgs/clustermsgs.go index d6cbf91fd2..912cf371c5 100644 --- a/pkg/apiservermsgs/clustermsgs.go +++ b/pkg/apiservermsgs/clustermsgs.go @@ -57,7 +57,7 @@ type CreateClusterRequest struct { PasswordReplication string Password string SecretFrom string - UserLabels string + UserLabels map[string]string Tablespaces []ClusterTablespaceDetail Policies string CCPImage string diff --git a/pkg/apiservermsgs/labelmsgs.go b/pkg/apiservermsgs/labelmsgs.go index d0a914840e..c28430c818 100644 --- a/pkg/apiservermsgs/labelmsgs.go +++ b/pkg/apiservermsgs/labelmsgs.go @@ -21,7 +21,7 @@ type LabelRequest struct { Selector string Namespace string Args []string - LabelCmdLabel string + Labels map[string]string DryRun bool DeleteLabel bool ClientVersion string @@ -33,7 +33,7 @@ type DeleteLabelRequest struct { Selector string Namespace string Args []string - LabelCmdLabel string + Labels map[string]string ClientVersion string } diff --git a/pkg/events/eventtype.go b/pkg/events/eventtype.go index ebecda8055..9781277883 100644 --- a/pkg/events/eventtype.go +++ b/pkg/events/eventtype.go @@ -52,7 +52,6 @@ const ( EventUpgradeClusterFailure = "UpgradeClusterFailure" EventDeleteCluster = "DeleteCluster" EventDeleteClusterCompleted = "DeleteClusterCompleted" - EventCreateLabel = "CreateLabel" EventCreateBackup = "CreateBackup" EventCreateBackupCompleted = "CreateBackupCompleted" @@ -313,22 +312,6 @@ func (lvl EventCreateBackupCompletedFormat) String() string { return msg } -//---------------------------- -type EventCreateLabelFormat struct { - EventHeader `json:"eventheader"` - Clustername string `json:"clustername"` - Label string `json:"label"` -} - -func (p EventCreateLabelFormat) GetHeader() EventHeader { - return p.EventHeader -} - -func (lvl EventCreateLabelFormat) String() string { - msg := fmt.Sprintf("Event %s (create label) - clustername %s - label [%s]", lvl.EventHeader, lvl.Clustername, lvl.Label) - return msg -} - //---------------------------- type EventCreatePolicyFormat struct { EventHeader `json:"eventheader"` From b0395a52779b9ab5d1ee93ea44bb3955b8f71b35 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 19 Jan 2021 11:17:52 -0500 Subject: [PATCH 157/276] Bump v4.6.0-rc.1 --- Makefile | 2 +- bin/push-ccp-to-gcr.sh | 2 +- conf/postgres-operator/pgo.yaml | 4 ++-- docs/config.toml | 2 +- docs/content/releases/4.6.0.md | 6 ++++++ examples/create-by-resource/fromcrd.json | 6 +++--- examples/envs.sh | 2 +- examples/helm/README.md | 2 +- examples/helm/postgres/Chart.yaml | 2 +- examples/helm/postgres/templates/pgcluster.yaml | 2 +- examples/helm/postgres/values.yaml | 2 +- examples/kustomize/createcluster/README.md | 16 ++++++++-------- .../kustomize/createcluster/base/pgcluster.yaml | 6 +++--- .../overlay/staging/hippo-rpl1-pgreplica.yaml | 2 +- installers/ansible/README.md | 2 +- installers/ansible/values.yaml | 6 +++--- installers/gcp-marketplace/Makefile | 2 +- installers/gcp-marketplace/README.md | 2 +- installers/gcp-marketplace/values.yaml | 6 +++--- installers/helm/Chart.yaml | 2 +- installers/helm/values.yaml | 6 +++--- installers/kubectl/client-setup.sh | 2 +- installers/kubectl/postgres-operator-ocp311.yml | 8 ++++---- installers/kubectl/postgres-operator.yml | 8 ++++---- installers/metrics/ansible/README.md | 2 +- installers/metrics/helm/Chart.yaml | 2 +- installers/metrics/helm/helm_template.yaml | 2 +- installers/metrics/helm/values.yaml | 2 +- .../kubectl/postgres-operator-metrics-ocp311.yml | 2 +- .../kubectl/postgres-operator-metrics.yml | 2 +- installers/olm/Makefile | 2 +- pkg/apis/crunchydata.com/v1/doc.go | 8 ++++---- pkg/apiservermsgs/common.go | 2 +- redhat/atomic/help.1 | 2 +- redhat/atomic/help.md | 2 +- 35 files changed, 67 insertions(+), 61 deletions(-) diff --git a/Makefile b/Makefile index bdaf771968..86b7166458 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ PGOROOT ?= $(CURDIR) PGO_BASEOS ?= centos8 PGO_IMAGE_PREFIX ?= crunchydata PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) -PGO_VERSION ?= 4.6.0-beta.3 +PGO_VERSION ?= 4.6.0-rc.1 PGO_PG_VERSION ?= 13 PGO_PG_FULLVERSION ?= 13.1 PGO_BACKREST_VERSION ?= 2.31 diff --git a/bin/push-ccp-to-gcr.sh b/bin/push-ccp-to-gcr.sh index 3c98e3829a..28ecdadeb6 100755 --- a/bin/push-ccp-to-gcr.sh +++ b/bin/push-ccp-to-gcr.sh @@ -16,7 +16,7 @@ GCR_IMAGE_PREFIX=gcr.io/crunchy-dev-test CCP_IMAGE_PREFIX=crunchydata -CCP_IMAGE_TAG=centos8-13.1-4.6.0-beta.3 +CCP_IMAGE_TAG=centos8-13.1-4.6.0-rc.1 IMAGES=( crunchy-prometheus diff --git a/conf/postgres-operator/pgo.yaml b/conf/postgres-operator/pgo.yaml index c30e11f41d..e95da5d9d6 100644 --- a/conf/postgres-operator/pgo.yaml +++ b/conf/postgres-operator/pgo.yaml @@ -2,7 +2,7 @@ Cluster: CCPImagePrefix: registry.developers.crunchydata.com/crunchydata Metrics: false Badger: false - CCPImageTag: centos8-13.1-4.6.0-beta.3 + CCPImageTag: centos8-13.1-4.6.0-rc.1 Port: 5432 PGBadgerPort: 10000 ExporterPort: 9187 @@ -81,4 +81,4 @@ Storage: Pgo: Audit: false PGOImagePrefix: registry.developers.crunchydata.com/crunchydata - PGOImageTag: centos8-4.6.0-beta.3 + PGOImageTag: centos8-4.6.0-rc.1 diff --git a/docs/config.toml b/docs/config.toml index 83e3dc59e7..8d34478847 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -25,7 +25,7 @@ disableNavChevron = false # set true to hide next/prev chevron, default is false highlightClientSide = false # set true to use highlight.pack.js instead of the default hugo chroma highlighter menushortcutsnewtab = true # set true to open shortcuts links to a new tab/window enableGitInfo = true -operatorVersion = "4.6.0-beta.3" +operatorVersion = "4.6.0-rc.1" postgresVersion = "13.1" postgresVersion13 = "13.1" postgresVersion12 = "13.1" diff --git a/docs/content/releases/4.6.0.md b/docs/content/releases/4.6.0.md index ff1a886735..499101e086 100644 --- a/docs/content/releases/4.6.0.md +++ b/docs/content/releases/4.6.0.md @@ -162,6 +162,8 @@ These changes also include overall organization and build performance optimizati - `service-type`, which is now represented by the `serviceType` attribute. - `NodeLabelKey`/`NodeLabelValue`, which is now replaced by the `nodeAffinity` attribute. - `backrest-storage-type`, which is now represented with the `backrestStorageTypes` attribute. +- The `--labels` flag on `pgo create cluster` is removed and replaced with the `--label`, which can be specified multiple times. The API endpoint for `pgo create cluster` is also modified: labels must now be passed in as a set of key-value pairs. Please see the "Features" section for more details. +- The API endpoints for `pgo label` and `pgo delete label` is modified to accept a set of key/value pairs for the values of the `--label` flag. The API parameter for this is now called `Labels`. The `pgo upgrade` command will properly moved any data you have in these labels into the correct attributes. You can read more about how to use the various CRD attributes in the [Custom Resources](https://access.crunchydata.com/documentation/postgres-operator/latest/custom-resources/) section of the documentation. - The `rootsecretname`, `primarysecretname`, and `usersecretname` attributes on the `pgclusters.crunchydata.com` CRD have been removed. Each of these represented managed Secrets. Additionally, if the managed Secrets are not created at cluster creation time, the Operator will now generate these Secrets. - The `collectSecretName` attribute on `pgclusters.crunchydata.com` has been removed. The Secret for the metrics collection user is now fully managed by the PostgreSQL Operator. @@ -193,6 +195,8 @@ Passing in the [`--process-max`](https://pgbackrest.org/command.html#command-arc - `pgo restore` will now first attempt a [pgBackRest delta restore](https://pgbackrest.org/user-guide.html#restore/option-delta), which can significantly speed up the restore time for large databases. Passing in the [`--process-max`](https://pgbackrest.org/command.html#command-archive-get/category-general/option-process-max) option to `--backup-opts` can help speed up the restore process based upon the amount of CPU you have available. - A pgBackRest backup can now be deleted with `pgo delete backup`. A backup name must be specified with the `--target` flag. Please refer to the [documentation](https://access.crunchydata.com/documentation/postgres-operator/latest/tutorial/disaster-recovery/#deleting-a-backup) for how to use this command. +- `pgo create cluster` now accepts a `--label` flag that can be used to specify one or more custom labels for a PostgreSQL cluster. This replaces the `--labels`flag. +- `pgo label` and `pgo delete label` can accept a `--label` flag specified multiple times. - pgBadger can now be enabled/disabled during the lifetime of a PostgreSQL cluster using the `pgo update --enable-pgbadger` and `pgo update --disable-pgbadger` flag. This can also be modified directly on a custom resource. - Managed PostgreSQL system accounts and now have their credentials set and rotated with `pgo update user` by including the `--set-system-account-password` flag. Suggested by (@srinathganesh). @@ -233,7 +237,9 @@ Passing in the [`--process-max`](https://pgbackrest.org/command.html#command-arc - Fix potential race condition that could lead to a crash in the Operator boot when an error is issued around loading the `pgo-config` ConfigMap. Reported by Aleksander Roszig (@AleksanderRoszig). - Do not trigger a backup if a standby cluster fails over. Reported by (@aprilito1965). - Ensure pgBouncer Secret is created when adding it to a standby cluster. +- Generally improvements to initialization of a standby cluster. - Remove legacy `defaultMode` setting on the volume instructions for the pgBackRest repo Secret as the `readOnly` setting is used on the mount itself. Reported by (@szhang1). +- Ensure proper label parsing based on Kubernetes rules and that it is consistently applied across all functionality that uses labels. Reported by José Joye (@jose-joye). - The logger no longer defaults to using a log level of `DEBUG`. - Autofailover is no longer disabled when an `rmdata` Job is run, enabling a clean database shutdown process when deleting a PostgreSQL cluster. - Allow for `Restart` API server permission to be explicitly set. Reported by Aleksander Roszig (@AleksanderRoszig). diff --git a/examples/create-by-resource/fromcrd.json b/examples/create-by-resource/fromcrd.json index d83b9cd810..2876f4c248 100644 --- a/examples/create-by-resource/fromcrd.json +++ b/examples/create-by-resource/fromcrd.json @@ -10,7 +10,7 @@ "deployment-name": "fromcrd", "name": "fromcrd", "pg-cluster": "fromcrd", - "pgo-version": "4.6.0-beta.3", + "pgo-version": "4.6.0-rc.1", "pgouser": "pgoadmin" }, "name": "fromcrd", @@ -45,7 +45,7 @@ "supplementalgroups": "" }, "ccpimage": "crunchy-postgres-ha", - "ccpimagetag": "centos8-13.1-4.6.0-beta.3", + "ccpimagetag": "centos8-13.1-4.6.0-rc.1", "clustername": "fromcrd", "database": "userdb", "exporterport": "9187", @@ -60,7 +60,7 @@ "port": "5432", "user": "testuser", "userlabels": { - "pgo-version": "4.6.0-beta.3" + "pgo-version": "4.6.0-rc.1" } } } diff --git a/examples/envs.sh b/examples/envs.sh index ebcd9798a0..c26dc96310 100644 --- a/examples/envs.sh +++ b/examples/envs.sh @@ -20,7 +20,7 @@ export PGO_CONF_DIR=$PGOROOT/installers/ansible/roles/pgo-operator/files # the version of the Operator you run is set by these vars export PGO_IMAGE_PREFIX=registry.developers.crunchydata.com/crunchydata export PGO_BASEOS=centos8 -export PGO_VERSION=4.6.0-beta.3 +export PGO_VERSION=4.6.0-rc.1 export PGO_IMAGE_TAG=$PGO_BASEOS-$PGO_VERSION # for setting the pgo apiserver port, disabling TLS or not verifying TLS diff --git a/examples/helm/README.md b/examples/helm/README.md index 04ab5211aa..75d7cf2123 100644 --- a/examples/helm/README.md +++ b/examples/helm/README.md @@ -64,7 +64,7 @@ The following values can also be set: - `ha`: Whether or not to deploy a high availability PostgreSQL cluster. Can be either `true` or `false`, defaults to `false`. - `imagePrefix`: The prefix of the container images to use for this PostgreSQL cluster. Default to `registry.developers.crunchydata.com/crunchydata`. - `image`: The name of the container image to use for the PostgreSQL cluster. Defaults to `crunchy-postgres-ha`. -- `imageTag`: The container image tag to use. Defaults to `centos8-13.1-4.6.0-beta.3`. +- `imageTag`: The container image tag to use. Defaults to `centos8-13.1-4.6.0-rc.1`. - `memory`: The memory limit for the PostgreSQL cluster. Follows standard Kubernetes formatting. - `monitoring`: Whether or not to enable monitoring / metrics collection for this PostgreSQL instance. Can either be `true` or `false`, defaults to `false`. diff --git a/examples/helm/postgres/Chart.yaml b/examples/helm/postgres/Chart.yaml index 7a6b0a2287..fe2a16284c 100644 --- a/examples/helm/postgres/Chart.yaml +++ b/examples/helm/postgres/Chart.yaml @@ -20,4 +20,4 @@ version: 0.2.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. -appVersion: 4.6.0-beta.3 +appVersion: 4.6.0-rc.1 diff --git a/examples/helm/postgres/templates/pgcluster.yaml b/examples/helm/postgres/templates/pgcluster.yaml index 886d6d43d5..83c3519af9 100644 --- a/examples/helm/postgres/templates/pgcluster.yaml +++ b/examples/helm/postgres/templates/pgcluster.yaml @@ -28,7 +28,7 @@ spec: storagetype: dynamic ccpimage: {{ .Values.image | default "crunchy-postgres-ha" | quote }} ccpimageprefix: {{ .Values.imagePrefix | default "registry.developers.crunchydata.com/crunchydata" | quote }} - ccpimagetag: {{ .Values.imageTag | default "centos8-13.1-4.6.0-beta.3" | quote }} + ccpimagetag: {{ .Values.imageTag | default "centos8-13.1-4.6.0-rc.1" | quote }} clustername: {{ .Values.name | quote }} database: {{ .Values.name | quote }} {{- if .Values.monitoring }} diff --git a/examples/helm/postgres/values.yaml b/examples/helm/postgres/values.yaml index b1a541dbb3..0af1278336 100644 --- a/examples/helm/postgres/values.yaml +++ b/examples/helm/postgres/values.yaml @@ -10,5 +10,5 @@ password: W4tch0ut4hippo$ # ha: true # imagePrefix: registry.developers.crunchydata.com/crunchydata # image: crunchy-postgres-ha -# imageTag: centos8-13.1-4.6.0-beta.3 +# imageTag: centos8-13.1-4.6.0-rc.1 # memory: 1Gi diff --git a/examples/kustomize/createcluster/README.md b/examples/kustomize/createcluster/README.md index ba466f62eb..3fe3244bb5 100644 --- a/examples/kustomize/createcluster/README.md +++ b/examples/kustomize/createcluster/README.md @@ -44,13 +44,13 @@ pgo show cluster hippo -n pgo ``` You will see something like this if successful: ``` -cluster : hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.3) +cluster : hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-rc.1) pod : hippo-8fb6bd96-j87wq (Running) on gke-xxxx-default-pool-38e946bd-257w (1/1) (primary) pvc: hippo (1Gi) deployment : hippo deployment : hippo-backrest-shared-repo service : hippo - ClusterIP (10.0.56.86) - Ports (2022/TCP, 5432/TCP) - labels : pgo-version=4.6.0-beta.3 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.0-rc.1 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata ``` Feel free to run other pgo cli commands on the hippo cluster @@ -79,7 +79,7 @@ pgo show cluster dev-hippo -n pgo ``` You will see something like this if successful: ``` -cluster : dev-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.3) +cluster : dev-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-rc.1) pod : dev-hippo-588d4cb746-bwrxb (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: dev-hippo (1Gi) deployment : dev-hippo @@ -87,7 +87,7 @@ cluster : dev-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.3) deployment : dev-hippo-pgbouncer service : dev-hippo - ClusterIP (10.0.62.87) - Ports (2022/TCP, 5432/TCP) service : dev-hippo-pgbouncer - ClusterIP (10.0.48.120) - Ports (5432/TCP) - labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.6.0-beta.3 pgouser=admin + labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.6.0-rc.1 pgouser=admin ``` #### staging The staging overlay will deploy a crunchy postgreSQL cluster with 2 replica's with annotations added @@ -113,7 +113,7 @@ pgo show cluster staging-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : staging-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.3) +cluster : staging-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-rc.1) pod : staging-hippo-85cf6dcb65-9h748 (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: staging-hippo (1Gi) pod : staging-hippo-lnxw-cf47d8c8b-6r4wn (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (replica) @@ -128,7 +128,7 @@ cluster : staging-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.3) service : staging-hippo-replica - ClusterIP (10.0.56.57) - Ports (2022/TCP, 5432/TCP) pgreplica : staging-hippo-lnxw pgreplica : staging-hippo-rpl1 - labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.6.0-beta.3 pgouser=admin vendor=crunchydata + labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.6.0-rc.1 pgouser=admin vendor=crunchydata ``` #### production @@ -154,7 +154,7 @@ pgo show cluster prod-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : prod-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.3) +cluster : prod-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-rc.1) pod : prod-hippo-5d6dd46497-rr67c (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (primary) pvc: prod-hippo (1Gi) pod : prod-hippo-flty-84d97c8769-2pzbh (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (replica) @@ -165,7 +165,7 @@ cluster : prod-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-beta.3) service : prod-hippo - ClusterIP (10.0.56.18) - Ports (2022/TCP, 5432/TCP) service : prod-hippo-replica - ClusterIP (10.0.56.101) - Ports (2022/TCP, 5432/TCP) pgreplica : prod-hippo-flty - labels : pgo-version=4.6.0-beta.3 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.0-rc.1 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata ``` ### Delete the clusters To delete the clusters run the following pgo cli commands diff --git a/examples/kustomize/createcluster/base/pgcluster.yaml b/examples/kustomize/createcluster/base/pgcluster.yaml index 00dbc793a7..53c874c670 100644 --- a/examples/kustomize/createcluster/base/pgcluster.yaml +++ b/examples/kustomize/createcluster/base/pgcluster.yaml @@ -10,7 +10,7 @@ metadata: deployment-name: hippo name: hippo pg-cluster: hippo - pgo-version: 4.6.0-beta.3 + pgo-version: 4.6.0-rc.1 pgouser: admin name: hippo namespace: pgo @@ -42,7 +42,7 @@ spec: annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: registry.developers.crunchydata.com/crunchydata - ccpimagetag: centos8-13.1-4.6.0-beta.3 + ccpimagetag: centos8-13.1-4.6.0-rc.1 clustername: hippo customconfig: "" database: hippo @@ -63,4 +63,4 @@ spec: port: "5432" user: hippo userlabels: - pgo-version: 4.6.0-beta.3 + pgo-version: 4.6.0-rc.1 diff --git a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml index a9fcb3a2bf..f5b204e760 100644 --- a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml +++ b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml @@ -20,4 +20,4 @@ spec: storagetype: dynamic supplementalgroups: "" userlabels: - pgo-version: 4.6.0-beta.3 + pgo-version: 4.6.0-rc.1 diff --git a/installers/ansible/README.md b/installers/ansible/README.md index 88b151a5a3..5c63176c50 100644 --- a/installers/ansible/README.md +++ b/installers/ansible/README.md @@ -4,7 +4,7 @@ Crunchy Data

-Latest Release: 4.6.0-beta.3 +Latest Release: 4.6.0-rc.1 ## General diff --git a/installers/ansible/values.yaml b/installers/ansible/values.yaml index ad8942500f..03cf3f10b4 100644 --- a/installers/ansible/values.yaml +++ b/installers/ansible/values.yaml @@ -17,7 +17,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.1-4.6.0-beta.3" +ccp_image_tag: "centos8-13.1-4.6.0-rc.1" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -50,14 +50,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.0-beta.3" +pgo_client_version: "4.6.0-rc.1" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos8-4.6.0-beta.3" +pgo_image_tag: "centos8-4.6.0-rc.1" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/gcp-marketplace/Makefile b/installers/gcp-marketplace/Makefile index d25a30c579..afb1b6eba1 100644 --- a/installers/gcp-marketplace/Makefile +++ b/installers/gcp-marketplace/Makefile @@ -6,7 +6,7 @@ MARKETPLACE_TOOLS ?= gcr.io/cloud-marketplace-tools/k8s/dev:$(MARKETPLACE_VERSIO MARKETPLACE_VERSION ?= 0.9.4 KUBECONFIG ?= $(HOME)/.kube/config PARAMETERS ?= {} -PGO_VERSION ?= 4.6.0-beta.3 +PGO_VERSION ?= 4.6.0-rc.1 IMAGE_BUILD_ARGS = --build-arg MARKETPLACE_VERSION='$(MARKETPLACE_VERSION)' \ --build-arg PGO_VERSION='$(PGO_VERSION)' diff --git a/installers/gcp-marketplace/README.md b/installers/gcp-marketplace/README.md index b96a602f73..b9d6a8356f 100644 --- a/installers/gcp-marketplace/README.md +++ b/installers/gcp-marketplace/README.md @@ -59,7 +59,7 @@ Google Cloud Marketplace. ```shell IMAGE_REPOSITORY=gcr.io/crunchydata-public/postgres-operator - export PGO_VERSION=4.6.0-beta.3 + export PGO_VERSION=4.6.0-rc.1 export INSTALLER_IMAGE=${IMAGE_REPOSITORY}/deployer:${PGO_VERSION} export OPERATOR_IMAGE=${IMAGE_REPOSITORY}:${PGO_VERSION} export OPERATOR_IMAGE_API=${IMAGE_REPOSITORY}/pgo-apiserver:${PGO_VERSION} diff --git a/installers/gcp-marketplace/values.yaml b/installers/gcp-marketplace/values.yaml index 762bc7e951..19661b4702 100644 --- a/installers/gcp-marketplace/values.yaml +++ b/installers/gcp-marketplace/values.yaml @@ -10,7 +10,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.1-4.6.0-beta.3" +ccp_image_tag: "centos8-13.1-4.6.0-rc.1" create_rbac: "true" db_name: "" db_password_age_days: "0" @@ -32,9 +32,9 @@ pgo_admin_role_name: "pgoadmin" pgo_admin_username: "admin" pgo_client_container_install: "false" pgo_client_install: 'false' -pgo_client_version: "4.6.0-beta.3" +pgo_client_version: "4.6.0-rc.1" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.0-beta.3" +pgo_image_tag: "centos8-4.6.0-rc.1" pgo_installation_name: '${OPERATOR_NAME}' pgo_operator_namespace: '${OPERATOR_NAMESPACE}' scheduler_timeout: "3600" diff --git a/installers/helm/Chart.yaml b/installers/helm/Chart.yaml index 9224ba438b..6108bd738e 100644 --- a/installers/helm/Chart.yaml +++ b/installers/helm/Chart.yaml @@ -3,7 +3,7 @@ name: postgres-operator description: Crunchy PostgreSQL Operator Helm chart for Kubernetes type: application version: 0.2.0 -appVersion: 4.6.0-beta.3 +appVersion: 4.6.0-rc.1 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/crunchy_logo.png keywords: diff --git a/installers/helm/values.yaml b/installers/helm/values.yaml index ac0c551af2..563cbc94da 100644 --- a/installers/helm/values.yaml +++ b/installers/helm/values.yaml @@ -37,7 +37,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.1-4.6.0-beta.3" +ccp_image_tag: "centos8-13.1-4.6.0-rc.1" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -70,14 +70,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.0-beta.3" +pgo_client_version: "4.6.0-rc.1" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos8-4.6.0-beta.3" +pgo_image_tag: "centos8-4.6.0-rc.1" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/kubectl/client-setup.sh b/installers/kubectl/client-setup.sh index 7306dffe42..020bfa7fc0 100755 --- a/installers/kubectl/client-setup.sh +++ b/installers/kubectl/client-setup.sh @@ -14,7 +14,7 @@ # This script should be run after the operator has been deployed PGO_OPERATOR_NAMESPACE="${PGO_OPERATOR_NAMESPACE:-pgo}" PGO_USER_ADMIN="${PGO_USER_ADMIN:-pgouser-admin}" -PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.0-beta.3}" +PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.0-rc.1}" PGO_CLIENT_URL="https://github.com/CrunchyData/postgres-operator/releases/download/${PGO_CLIENT_VERSION}" PGO_CMD="${PGO_CMD-kubectl}" diff --git a/installers/kubectl/postgres-operator-ocp311.yml b/installers/kubectl/postgres-operator-ocp311.yml index 5436f123da..a35230a1a0 100644 --- a/installers/kubectl/postgres-operator-ocp311.yml +++ b/installers/kubectl/postgres-operator-ocp311.yml @@ -44,7 +44,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos8-13.1-4.6.0-beta.3" + ccp_image_tag: "centos8-13.1-4.6.0-rc.1" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -77,14 +77,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.0-beta.3" + pgo_client_version: "4.6.0-rc.1" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos8-4.6.0-beta.3" + pgo_image_tag: "centos8-4.6.0-rc.1" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -161,7 +161,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-beta.3 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-rc.1 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/kubectl/postgres-operator.yml b/installers/kubectl/postgres-operator.yml index 85ddb15e10..f890a89055 100644 --- a/installers/kubectl/postgres-operator.yml +++ b/installers/kubectl/postgres-operator.yml @@ -139,7 +139,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos8-13.1-4.6.0-beta.3" + ccp_image_tag: "centos8-13.1-4.6.0-rc.1" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -172,14 +172,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.0-beta.3" + pgo_client_version: "4.6.0-rc.1" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos8-4.6.0-beta.3" + pgo_image_tag: "centos8-4.6.0-rc.1" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -269,7 +269,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-beta.3 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-rc.1 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/ansible/README.md b/installers/metrics/ansible/README.md index 9a93e81f72..57dbdb8b6a 100644 --- a/installers/metrics/ansible/README.md +++ b/installers/metrics/ansible/README.md @@ -4,7 +4,7 @@ Crunchy Data

-Latest Release: 4.6.0-beta.3 +Latest Release: 4.6.0-rc.1 ## General diff --git a/installers/metrics/helm/Chart.yaml b/installers/metrics/helm/Chart.yaml index e954f3e465..66c4aeecf9 100644 --- a/installers/metrics/helm/Chart.yaml +++ b/installers/metrics/helm/Chart.yaml @@ -3,6 +3,6 @@ name: postgres-operator-monitoring description: Install for Crunchy PostgreSQL Operator Monitoring type: application version: 0.2.0 -appVersion: 4.6.0-beta.3 +appVersion: 4.6.0-rc.1 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/crunchy_logo.png diff --git a/installers/metrics/helm/helm_template.yaml b/installers/metrics/helm/helm_template.yaml index 135a132b19..12535de89a 100644 --- a/installers/metrics/helm/helm_template.yaml +++ b/installers/metrics/helm/helm_template.yaml @@ -20,5 +20,5 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.0-beta.3" +pgo_image_tag: "centos8-4.6.0-rc.1" diff --git a/installers/metrics/helm/values.yaml b/installers/metrics/helm/values.yaml index 0c5863629a..88e9f42582 100644 --- a/installers/metrics/helm/values.yaml +++ b/installers/metrics/helm/values.yaml @@ -20,7 +20,7 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.0-beta.3" +pgo_image_tag: "centos8-4.6.0-rc.1" # ===================== # Configuration Options diff --git a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml index c4d13f9b1c..026ad5d04a 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml @@ -96,7 +96,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-beta.3 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-rc.1 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/kubectl/postgres-operator-metrics.yml b/installers/metrics/kubectl/postgres-operator-metrics.yml index 0bd8dee095..c999653f38 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics.yml @@ -165,7 +165,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-beta.3 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-rc.1 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/olm/Makefile b/installers/olm/Makefile index df7dd962c5..e3482a7eef 100644 --- a/installers/olm/Makefile +++ b/installers/olm/Makefile @@ -11,7 +11,7 @@ OLM_TOOLS ?= registry.localhost:5000/postgres-operator-olm-tools:$(OLM_SDK_VERSI OLM_VERSION ?= 0.15.1 PGO_BASEOS ?= centos8 PGO_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -PGO_VERSION ?= 4.6.0-beta.3 +PGO_VERSION ?= 4.6.0-rc.1 PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) CCP_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(PGO_VERSION) CCP_POSTGIS_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(CCP_POSTGIS_VERSION)-$(PGO_VERSION) diff --git a/pkg/apis/crunchydata.com/v1/doc.go b/pkg/apis/crunchydata.com/v1/doc.go index eb6528dfd3..8c7a21173a 100644 --- a/pkg/apis/crunchydata.com/v1/doc.go +++ b/pkg/apis/crunchydata.com/v1/doc.go @@ -53,7 +53,7 @@ cluster. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.0-beta.3", + '{"ClientVersion":"4.6.0-rc.1", "Namespace":"pgouser1", "Name":"mycluster", $PGO_APISERVER_URL/clusters @@ -72,7 +72,7 @@ show all of the clusters that are in the given namespace. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.0-beta.3", + '{"ClientVersion":"4.6.0-rc.1", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/showclusters @@ -82,7 +82,7 @@ $PGO_APISERVER_URL/showclusters curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.0-beta.3", + '{"ClientVersion":"4.6.0-rc.1", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/clustersdelete @@ -90,7 +90,7 @@ $PGO_APISERVER_URL/clustersdelete Schemes: http, https BasePath: / - Version: 4.6.0-beta.3 + Version: 4.6.0-rc.1 License: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0 Contact: Crunchy Data https://www.crunchydata.com/ diff --git a/pkg/apiservermsgs/common.go b/pkg/apiservermsgs/common.go index 5cef464f5f..3d24393610 100644 --- a/pkg/apiservermsgs/common.go +++ b/pkg/apiservermsgs/common.go @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -const PGO_VERSION = "4.6.0-beta.3" +const PGO_VERSION = "4.6.0-rc.1" // Ok status const Ok = "ok" diff --git a/redhat/atomic/help.1 b/redhat/atomic/help.1 index 2f1de01426..d94801c5cc 100644 --- a/redhat/atomic/help.1 +++ b/redhat/atomic/help.1 @@ -56,4 +56,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa \fB\fCRelease=\fR .PP -The specific release number of the container. For example, Release="4.6.0-beta.3" +The specific release number of the container. For example, Release="4.6.0-rc.1" diff --git a/redhat/atomic/help.md b/redhat/atomic/help.md index 7101976e61..f0889e1cdf 100644 --- a/redhat/atomic/help.md +++ b/redhat/atomic/help.md @@ -45,4 +45,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa `Release=` -The specific release number of the container. For example, Release="4.6.0-beta.3" +The specific release number of the container. For example, Release="4.6.0-rc.1" From 98529f2e769b15a63a698463b75fa74f0f0e28df Mon Sep 17 00:00:00 2001 From: Joseph Mckulka <16840147+jmckulk@users.noreply.github.com> Date: Tue, 19 Jan 2021 15:41:50 -0500 Subject: [PATCH 158/276] Update pgMonitor path in pgo-deployer This allows for the successful installation of the Postgres Operator Monitoring stack in a variety of environments due to the various permission considerations. --- build/pgo-deployer/Dockerfile | 3 +- .../roles/pgo-metrics/tasks/alertmanager.yml | 2 +- .../roles/pgo-metrics/tasks/grafana.yml | 2 +- .../ansible/roles/pgo-metrics/tasks/main.yml | 33 ++++++++++++++++--- .../roles/pgo-metrics/tasks/prometheus.yml | 2 +- 5 files changed, 32 insertions(+), 10 deletions(-) diff --git a/build/pgo-deployer/Dockerfile b/build/pgo-deployer/Dockerfile index 79c3516d2c..fdfeb39104 100644 --- a/build/pgo-deployer/Dockerfile +++ b/build/pgo-deployer/Dockerfile @@ -70,7 +70,7 @@ fi COPY installers/ansible /ansible/postgres-operator COPY installers/metrics/ansible /ansible/metrics -ADD tools/pgmonitor /tmp/.pgo/metrics/pgmonitor +ADD tools/pgmonitor /opt/crunchy/pgmonitor COPY installers/image/bin/pgo-deploy.sh /pgo-deploy.sh COPY bin/uid_daemon.sh /uid_daemon.sh @@ -79,7 +79,6 @@ ENV HOME="/tmp" RUN chmod g=u /etc/passwd RUN chmod g=u /uid_daemon.sh -RUN chown -R 2:2 /tmp/.pgo ENTRYPOINT ["/uid_daemon.sh"] diff --git a/installers/metrics/ansible/roles/pgo-metrics/tasks/alertmanager.yml b/installers/metrics/ansible/roles/pgo-metrics/tasks/alertmanager.yml index 13fd013290..9c27302579 100644 --- a/installers/metrics/ansible/roles/pgo-metrics/tasks/alertmanager.yml +++ b/installers/metrics/ansible/roles/pgo-metrics/tasks/alertmanager.yml @@ -16,7 +16,7 @@ - name: Set pgmonitor Prometheus Directory Fact set_fact: - pgmonitor_prometheus_dir: "{{ metrics_dir }}/pgmonitor/prometheus" + pgmonitor_prometheus_dir: "{{ pgmonitor_dir }}/prometheus" - name: Copy Alertmanger Config to Output Directory command: "cp {{ pgmonitor_prometheus_dir }}/{{ item.src }} {{ alertmanager_output_dir }}/{{ item.dst }}" diff --git a/installers/metrics/ansible/roles/pgo-metrics/tasks/grafana.yml b/installers/metrics/ansible/roles/pgo-metrics/tasks/grafana.yml index f0b88e0c65..020e8cfa6d 100644 --- a/installers/metrics/ansible/roles/pgo-metrics/tasks/grafana.yml +++ b/installers/metrics/ansible/roles/pgo-metrics/tasks/grafana.yml @@ -48,7 +48,7 @@ - name: Set pgmonitor Grafana Directory Fact set_fact: - pgmonitor_grafana_dir: "{{ metrics_dir }}/pgmonitor/grafana" + pgmonitor_grafana_dir: "{{ pgmonitor_dir }}/grafana" - name: Copy Grafana Config to Output Directory command: "cp {{ pgmonitor_grafana_dir }}/{{ item }} {{ grafana_output_dir }}" diff --git a/installers/metrics/ansible/roles/pgo-metrics/tasks/main.yml b/installers/metrics/ansible/roles/pgo-metrics/tasks/main.yml index ae2c27b8e3..994bc6e9bd 100644 --- a/installers/metrics/ansible/roles/pgo-metrics/tasks/main.yml +++ b/installers/metrics/ansible/roles/pgo-metrics/tasks/main.yml @@ -56,21 +56,44 @@ block: - name: Check for pgmonitor stat: - path: "{{ metrics_dir }}/pgmonitor" - register: pgmonitor_dir + path: "/opt/crunchy/pgmonitor" + register: pgmonitor_dir_embed + + - name: Set pgMonitor Directory Fact + block: + - name: Embeded Path + set_fact: + pgmonitor_dir: "/opt/crunchy/pgmonitor" + when: pgmonitor_dir_embed.stat.exists + + - name: Downloaded Path + set_fact: + pgmonitor_dir: "{{ metrics_dir }}/pgmonitor" + when: not pgmonitor_dir_embed.stat.exists + + - name: Ensure pgMonitor Output Directory Exists + file: + path: "{{ pgmonitor_dir }}" + state: directory + mode: 0700 + when: not pgmonitor_dir_embed.stat.exists - name: Download pgmonitor {{ pgmonitor_version }} get_url: url: https://github.com/CrunchyData/pgmonitor/archive/{{ pgmonitor_version }}.tar.gz dest: "{{ metrics_dir }}" mode: "0600" - when: not pgmonitor_dir.stat.exists + when: not pgmonitor_dir_embed.stat.exists - name: Extract pgmonitor unarchive: src: "{{ metrics_dir }}/pgmonitor-{{ pgmonitor_version | replace('v','') }}.tar.gz" - dest: "{{ metrics_dir }}/pgmonitor" - when: not pgmonitor_dir.stat.exists + dest: "{{ metrics_dir }}" + when: not pgmonitor_dir_embed.stat.exists + + - name: Copy pgmonitor to correct directory + command: "cp -R {{ metrics_dir }}/pgmonitor-{{ pgmonitor_version | replace('v','') }}/. {{ pgmonitor_dir }}" + when: not pgmonitor_dir_embed.stat.exists - name: Create Metrics Image Pull Secret shell: > diff --git a/installers/metrics/ansible/roles/pgo-metrics/tasks/prometheus.yml b/installers/metrics/ansible/roles/pgo-metrics/tasks/prometheus.yml index b9d70aad1f..729fd4762e 100644 --- a/installers/metrics/ansible/roles/pgo-metrics/tasks/prometheus.yml +++ b/installers/metrics/ansible/roles/pgo-metrics/tasks/prometheus.yml @@ -35,7 +35,7 @@ - name: Set pgmonitor Prometheus Directory Fact set_fact: - pgmonitor_prometheus_dir: "{{ metrics_dir }}/pgmonitor/prometheus" + pgmonitor_prometheus_dir: "{{ pgmonitor_dir }}/prometheus" - name: Copy Prometheus Config to Output Directory command: "cp {{ pgmonitor_prometheus_dir }}/{{ item.src }} {{ prom_output_dir }}/{{ item.dst }}" From ec8d91166dc1abcc57259b422a309739cbc26a91 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Wed, 20 Jan 2021 21:28:52 -0500 Subject: [PATCH 159/276] Update custom configuration docs to clearly specify creation case This was handled in the tutorial, but not in the "Advanced Topics" section. Issue: #2222 --- docs/content/advanced/custom-configuration.md | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/docs/content/advanced/custom-configuration.md b/docs/content/advanced/custom-configuration.md index 5a7e4f0e34..b1df1c81b0 100644 --- a/docs/content/advanced/custom-configuration.md +++ b/docs/content/advanced/custom-configuration.md @@ -75,6 +75,46 @@ files that ship with the Crunchy Postgres container, there is no requirement to. In this event, continue using the Operator as usual and avoid defining a global configMap. +## Create a PostgreSQL Cluster With Custom Configuration + +The PostgreSQL Operator allows for a PostgreSQL cluster to be created with a customized configuration. To do this, one must create a ConfigMap with an entry called `postgres-ha.yaml` that contains the custom configuration. The custom configuration follows the [Patorni YAML format](https://access.crunchydata.com/documentation/patroni/latest/settings/). Note that parameters that are placed in the `bootstrap` section are applied once during cluster initialization. Editing these values in a working cluster require following the [Modifying PostgreSQL Cluster Configuration](#modifying-postgresql-cluster-configuration) section. + +For example, let's say we want to create a PostgreSQL cluster with `shared_buffers` set to `2GB`, `max_connections` set to `30` and `password_encryption` set to `scram-sha-256`. We would create a configuration file that looks similar to: + +``` +--- +bootstrap: + dcs: + postgresql: + parameters: + max_connections: 30 + shared_buffers: 2GB + password_encryption: scram-sha-256 +``` + +Save this configuration in a file called `postgres-ha.yaml`. + +Create a [`ConfigMap`](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/) like so: + +``` +kubectl -n pgo create configmap hippo-custom-config --from-file=postgres-ha.yaml +``` + +You can then have you new PostgreSQL cluster use `hippo-custom-config` as part of its cluster initialization by using the `--custom-config` flag of `pgo create cluster`: + +``` +pgo create cluster hippo -n pgo --custom-config=hippo-custom-config +``` + +After your cluster is initialized, [connect to your cluster]({{< relref "tutorial/connect-cluster.md" >}}) and confirm that your settings have been applied: + +``` +SHOW shared_buffers; + + shared_buffers +---------------- + 2GB +``` ## Modifying PostgreSQL Cluster Configuration From 731715213cc9abd86e2838357861c553c555ef73 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Thu, 21 Jan 2021 22:36:38 -0500 Subject: [PATCH 160/276] Clarification in documentation around custom resource use. This cleans up some language that no longer applies to the custom resources, and modifies the examples slightly. --- .../architecture/high-availability/_index.md | 1 + docs/content/custom-resources/_index.md | 33 ++++++++----------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/docs/content/architecture/high-availability/_index.md b/docs/content/architecture/high-availability/_index.md index 073df5e599..a2ab9b0148 100644 --- a/docs/content/architecture/high-availability/_index.md +++ b/docs/content/architecture/high-availability/_index.md @@ -418,5 +418,6 @@ modification to the custom resource: - CPU resource adjustments - Custom annotation changes - Enabling/disabling the monitoring sidecar on a PostgreSQL cluster (`--metrics`) +- Enabling/disabling the pgBadger sidecar on a PostgreSQL cluster (`--pgbadger`) - Tablespace additions - Toleration modifications diff --git a/docs/content/custom-resources/_index.md b/docs/content/custom-resources/_index.md index 43341006df..f0c5c8a5d4 100644 --- a/docs/content/custom-resources/_index.md +++ b/docs/content/custom-resources/_index.md @@ -40,10 +40,8 @@ when manipulating the PostgreSQL Operator Custom Resources directly. ### Create a PostgreSQL Cluster The fundamental workflow for interfacing with a PostgreSQL Operator Custom -Resource Definition is for creating a PostgreSQL cluster. However, this is also -one of the most complicated workflows to go through, as there are several -Kubernetes objects that need to be created prior to using this method. These -include: +Resource Definition is for creating a PostgreSQL cluster. There are several +that a PostgreSQL cluster requires to be deployed, including: - Secrets - Information for setting up a pgBackRest repository @@ -54,14 +52,11 @@ include: Additionally, if you want to add some of the other sidecars, you may need to create additional secrets. -The following guide goes through how to create a PostgreSQL cluster called -`hippo` by creating a new custom resource. - -The below manifest references the Secrets created in the previous step to add a -custom resource to the `pgclusters.crunchydata.com` custom resource definition. +The good news is that if you do not provide these objects, the PostgreSQL +Operator will create them for you to get your Postgres cluster up and running! -**NOTE**: You will need to modify the storage sections to match your storage -configuration. +The following goes through how to create a PostgreSQL cluster called +`hippo` by creating a new custom resource. ``` # this variable is the name of the cluster being created @@ -91,7 +86,7 @@ spec: name: "" size: 1G storageclass: "" - storagetype: create + storagetype: dynamic supplementalgroups: "" PrimaryStorage: accessmode: ReadWriteMany @@ -99,7 +94,7 @@ spec: name: ${pgo_cluster_name} size: 1G storageclass: "" - storagetype: create + storagetype: dynamic supplementalgroups: "" ReplicaStorage: accessmode: ReadWriteMany @@ -107,7 +102,7 @@ spec: name: "" size: 1G storageclass: "" - storagetype: create + storagetype: dynamic supplementalgroups: "" annotations: {} ccpimage: crunchy-postgres-ha @@ -406,7 +401,7 @@ spec: name: "" size: 1G storageclass: "" - storagetype: create + storagetype: dynamic supplementalgroups: "" PrimaryStorage: accessmode: ReadWriteMany @@ -414,7 +409,7 @@ spec: name: ${pgo_cluster_name} size: 1G storageclass: "" - storagetype: create + storagetype: dynamic supplementalgroups: "" ReplicaStorage: accessmode: ReadWriteMany @@ -422,7 +417,7 @@ spec: name: "" size: 1G storageclass: "" - storagetype: create + storagetype: dynamic supplementalgroups: "" annotations: {} ccpimage: crunchy-postgres-ha @@ -539,7 +534,7 @@ spec: name: ${pgo_cluster_name}-${pgo_cluster_replica_suffix} size: 1G storageclass: "" - storagetype: create + storagetype: dynamic supplementalgroups: "" tolerations: [] userlabels: @@ -588,7 +583,7 @@ tablespaceMounts: matchLabels: "" size: 5Gi storageclass: "" - storagetype: create + storagetype: dynamic supplementalgroups: "" ``` From ceba73cd7a1197e006e30fdd376f5cd0140dd345 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Thu, 21 Jan 2021 22:45:10 -0500 Subject: [PATCH 161/276] OLM updates for 4.6 Issue: [ch10170] --- installers/olm/README.md | 20 ++ installers/olm/description.openshift.md | 195 +++++++++++++++++- installers/olm/description.upstream.md | 179 +++++++++++++++- .../olm/postgresoperator.crd.examples.yaml | 49 ++++- installers/olm/verify.sh | 2 +- 5 files changed, 430 insertions(+), 15 deletions(-) diff --git a/installers/olm/README.md b/installers/olm/README.md index 207f85daa8..9cf336ddac 100644 --- a/installers/olm/README.md +++ b/installers/olm/README.md @@ -11,3 +11,23 @@ tests. Consult the [technical requirements][hub-contrib] when making changes. [olm-csv]: https://github.com/operator-framework/operator-lifecycle-manager/blob/master/doc/design/building-your-csv.md [OLM]: https://github.com/operator-framework/operator-lifecycle-manager [scorecard]: https://sdk.operatorframework.io/docs/scorecard/ + +## Testing + +### Setup + +``` +make docker-package docker-verify +``` + +``` +pip3 install yq +``` + +### Testing + +``` +make install-olm # install OLM framework +make package # build OLM package +make verify # verify OLM package +``` diff --git a/installers/olm/description.openshift.md b/installers/olm/description.openshift.md index 6b1e79184b..fe9cee374e 100644 --- a/installers/olm/description.openshift.md +++ b/installers/olm/description.openshift.md @@ -15,6 +15,9 @@ providing the essential features you need to keep your PostgreSQL clusters up an Set how long you want your backups retained for. Works great with very large databases! - **Monitoring**: Track the health of your PostgreSQL clusters using the open source [pgMonitor][] library. - **Clone**: Create new clusters from your existing clusters or backups with a single [`pgo create cluster --restore-from`][pgo-create-cluster] command. +- **TLS**: Secure communication between your applications and data servers by [enabling TLS for your PostgreSQL servers][pgo-task-tls], including the ability to enforce that all of your connections to use TLS. +- **Connection Pooling**: Use [pgBouncer][] for connection pooling +- **Affinity and Tolerations**: Have your PostgreSQL clusters deployed to [Kubernetes Nodes][k8s-nodes] of your preference with [node affinity][high-availability-node-affinity], or designate which nodes Kubernetes can schedule PostgreSQL instances to with Kubneretes [tolerations][high-availability-tolerations]. - **Full Customizability**: Crunchy PostgreSQL for OpenShift makes it easy to get your own PostgreSQL-as-a-Service up and running on and lets make further enhancements to customize your deployments, including: - Selecting different storage classes for your primary, replica, and backup storage @@ -27,16 +30,20 @@ and much more! [disaster-recovery]: https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/disaster-recovery/ [high-availability]: https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/high-availability/ +[high-availability-node-affinity]: https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/high-availability/#node-affinity +[high-availability-tolerations]: https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/high-availability/#tolerations [pgo-create-cluster]: https://access.crunchydata.com/documentation/postgres-operator/latest/pgo-client/reference/pgo_create_cluster/ +[pgo-task-tls]: https://access.crunchydata.com/documentation/postgres-operator/latest/tutorial/tls/ [provisioning]: https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/provisioning/ [k8s-anti-affinity]: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#inter-pod-affinity-and-anti-affinity +[k8s-nodes]: https://kubernetes.io/docs/concepts/architecture/nodes/ [pgBackRest]: https://www.pgbackrest.org +[pgBouncer]: https://access.crunchydata.com/documentation/postgres-operator/latest/tutorial/pgbouncer/ [pgMonitor]: https://github.com/CrunchyData/pgmonitor - -## Before You Begin +## Pre-Installation There are a few manual steps that the cluster administrator must perform prior to installing the PostgreSQL Operator. At the very least, it must be provided with an initial configuration. @@ -80,8 +87,156 @@ oc -n "$PGO_OPERATOR_NAMESPACE" create secret tls pgo.tls \ Once these resources are in place, the PostgreSQL Operator can be installed into the cluster. +## Installation + +You can now go ahead and install the PostgreSQL Operator from OperatorHub. + +### Security + +For the PostgreSQL Operator and PostgreSQL clusters to run in the recommended `restricted` [Security Context Constraint][], +edit the ConfigMap `pgo-config`, find the `pgo.yaml` entry, and set `DisableFSGroup` to `true`. + +[Security Context Constraint]: https://docs.openshift.com/container-platform/latest/authentication/managing-security-context-constraints.html + +You will have to scale the `postgres-operator` Deployment down and up for the above change to take effect: + +``` +oc -n pgo scale --replicas 0 deployment/postgres-operator +oc -n pgo scale --replicas 1 deployment/postgres-operator +``` + +## Post-Installation + +### Tutorial + +For a guide on how to perform many of the daily functions of the PostgreSQL Operator, we recommend that you read the [Postgres Operator tutorial][pgo-tutorial] + +[pgo-tutorial]: https://access.crunchydata.com/documentation/postgres-operator/latest/tutorial/create-cluster/ + +However, the below guide will show you how to create a Postgres cluster from a custom resource or from using the `pgo-client`. + +### Create a PostgreSQL Cluster from a Custom Resource + +The fundamental workflow for interfacing with a PostgreSQL Operator Custom +Resource Definition is for creating a PostgreSQL cluster. There are several +that a PostgreSQL cluster requires to be deployed, including: + +- Secrets + - Information for setting up a pgBackRest repository + - PostgreSQL superuser bootstrap credentials + - PostgreSQL replication user bootstrap credentials + - PostgresQL standard user bootstrap credentials + +Additionally, if you want to add some of the other sidecars, you may need to +create additional secrets. + +The good news is that if you do not provide these objects, the PostgreSQL +Operator will create them for you to get your Postgres cluster up and running! + +The following goes through how to create a PostgreSQL cluster called +`hippo` by creating a new custom resource. + +``` +# this variable is the name of the cluster being created +export pgo_cluster_name=hippo +# this variable is the namespace the cluster is being deployed into +export cluster_namespace=pgo +# this variable is set to the location of your image repository +export cluster_image_prefix=registry.developers.crunchydata.com/crunchydata + +cat <<-EOF > "${pgo_cluster_name}-pgcluster.yaml" +apiVersion: crunchydata.com/v1 +kind: Pgcluster +metadata: + annotations: + current-primary: ${pgo_cluster_name} + labels: + crunchy-pgha-scope: ${pgo_cluster_name} + deployment-name: ${pgo_cluster_name} + name: ${pgo_cluster_name} + pg-cluster: ${pgo_cluster_name} + pgo-version: ${PGO_VERSION} + pgouser: admin + name: ${pgo_cluster_name} + namespace: ${cluster_namespace} +spec: + BackrestStorage: + accessmode: ReadWriteMany + matchLabels: "" + name: "" + size: 1G + storageclass: "" + storagetype: create + supplementalgroups: "" + PrimaryStorage: + accessmode: ReadWriteMany + matchLabels: "" + name: ${pgo_cluster_name} + size: 1G + storageclass: "" + storagetype: create + supplementalgroups: "" + ReplicaStorage: + accessmode: ReadWriteMany + matchLabels: "" + name: "" + size: 1G + storageclass: "" + storagetype: create + supplementalgroups: "" + annotations: {} + ccpimage: crunchy-postgres-ha + ccpimageprefix: ${cluster_image_prefix} + ccpimagetag: centos8-13.1-${PGO_VERSION} + clustername: ${pgo_cluster_name} + database: ${pgo_cluster_name} + exporterport: "9187" + limits: {} + name: ${pgo_cluster_name} + namespace: ${cluster_namespace} + pgDataSource: + restoreFrom: "" + restoreOpts: "" + pgbadgerport: "10000" + pgoimageprefix: ${cluster_image_prefix} + podAntiAffinity: + default: preferred + pgBackRest: preferred + pgBouncer: preferred + port: "5432" + tolerations: [] + user: hippo + userlabels: + pgo-version: ${PGO_VERSION} +EOF + +oc apply -f "${pgo_cluster_name}-pgcluster.yaml" +``` + +And that's all! The PostgreSQL Operator will go ahead and create the cluster. + +If you have the PostgreSQL client `psql` installed on your host machine, you can +test connection to the PostgreSQL cluster using the following command: -## After You Install +``` +# namespace that the cluster is running in +export PGO_OPERATOR_NAMESPACE=pgo +# name of the cluster +export pgo_cluster_name=hippo +# name of the user whose password we want to get +export pgo_cluster_username=hippo + +# get the password of the user and set it to a recognized psql environmental variable +export PGPASSWORD=$(oc -n "${PGO_OPERATOR_NAMESPACE}" get secrets \ + "${pgo_cluster_name}-${pgo_cluster_username}-secret" -o "jsonpath={.data['password']}" | base64 -d) + +# set up a port-forward either in a new terminal, or in the same terminal in the background: +oc -n pgo port-forward svc/hippo 5432:5432 & + +psql -h localhost -U "${pgo_cluster_username}" "${pgo_cluster_name}" +``` + +### Create a PostgreSQL Cluster the `pgo` Client Once the PostgreSQL Operator is installed in your OpenShift cluster, you will need to do a few things to use the [PostgreSQL Operator Client][pgo-client]. @@ -123,3 +278,37 @@ pgo version # pgo client version ${PGO_VERSION} # pgo-apiserver version ${PGO_VERSION} ``` + + +You can then create a cluster with the `pgo` client as simply as this: + +``` +pgo create cluster -n pgo hippo +``` + +The cluster may take a few moments to provision. You can verify that the cluster is up and running by using the `pgo test` command: + +``` +pgo test cluster -n pgo hippo +``` + +If you have the PostgreSQL client `psql` installed on your host machine, you can +test connection to the PostgreSQL cluster using the following command: + +``` +# namespace that the cluster is running in +export PGO_OPERATOR_NAMESPACE=pgo +# name of the cluster +export pgo_cluster_name=hippo +# name of the user whose password we want to get +export pgo_cluster_username=hippo + +# get the password of the user and set it to a recognized psql environmental variable +export PGPASSWORD=$(kubectl -n "${PGO_OPERATOR_NAMESPACE}" get secrets \ + "${pgo_cluster_name}-${pgo_cluster_username}-secret" -o "jsonpath={.data['password']}" | base64 -d) + +# set up a port-forward either in a new terminal, or in the same terminal in the background: +kubectl -n pgo port-forward svc/hippo 5432:5432 & + +psql -h localhost -U "${pgo_cluster_username}" "${pgo_cluster_name}" +``` diff --git a/installers/olm/description.upstream.md b/installers/olm/description.upstream.md index 9851ee914c..bbad9621a7 100644 --- a/installers/olm/description.upstream.md +++ b/installers/olm/description.upstream.md @@ -15,6 +15,9 @@ providing the essential features you need to keep your PostgreSQL clusters up an Set how long you want your backups retained for. Works great with very large databases! - **Monitoring**: Track the health of your PostgreSQL clusters using the open source [pgMonitor][] library. - **Clone**: Create new clusters from your existing clusters or backups with a single [`pgo create cluster --restore-from`][pgo-create-cluster] command. +- **TLS**: Secure communication between your applications and data servers by [enabling TLS for your PostgreSQL servers][pgo-task-tls], including the ability to enforce that all of your connections to use TLS. +- **Connection Pooling**: Use [pgBouncer][] for connection pooling +- **Affinity and Tolerations**: Have your PostgreSQL clusters deployed to [Kubernetes Nodes][k8s-nodes] of your preference with [node affinity][high-availability-node-affinity], or designate which nodes Kubernetes can schedule PostgreSQL instances to with Kubneretes [tolerations][high-availability-tolerations]. - **Full Customizability**: Crunchy PostgreSQL for Kubernetes makes it easy to get your own PostgreSQL-as-a-Service up and running on and lets make further enhancements to customize your deployments, including: - Selecting different storage classes for your primary, replica, and backup storage @@ -27,16 +30,21 @@ and much more! [disaster-recovery]: https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/disaster-recovery/ [high-availability]: https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/high-availability/ +[high-availability-node-affinity]: https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/high-availability/#node-affinity +[high-availability-tolerations]: https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/high-availability/#tolerations [pgo-create-cluster]: https://access.crunchydata.com/documentation/postgres-operator/latest/pgo-client/reference/pgo_create_cluster/ +[pgo-task-tls]: https://access.crunchydata.com/documentation/postgres-operator/latest/tutorial/tls/ [provisioning]: https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/provisioning/ [k8s-anti-affinity]: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#inter-pod-affinity-and-anti-affinity +[k8s-nodes]: https://kubernetes.io/docs/concepts/architecture/nodes/ [pgBackRest]: https://www.pgbackrest.org +[pgBouncer]: https://access.crunchydata.com/documentation/postgres-operator/latest/tutorial/pgbouncer/ [pgMonitor]: https://github.com/CrunchyData/pgmonitor -## Before You Begin +## Pre-Installation There are a few manual steps that the cluster administrator must perform prior to installing the PostgreSQL Operator. At the very least, it must be provided with an initial configuration. @@ -73,8 +81,142 @@ kubectl -n "$PGO_OPERATOR_NAMESPACE" create secret tls pgo.tls \ Once these resources are in place, the PostgreSQL Operator can be installed into the cluster. +## Installation -## After You Install +You can now go ahead and install the PostgreSQL Operator from OperatorHub. + +## Post-Installation + +### Tutorial + +For a guide on how to perform many of the daily functions of the PostgreSQL Operator, we recommend that you read the [Postgres Operator tutorial][pgo-tutorial] + +[pgo-tutorial]: https://access.crunchydata.com/documentation/postgres-operator/latest/tutorial/create-cluster/ + +However, the below guide will show you how to create a Postgres cluster from a custom resource or from using the `pgo-client`. + +### Create a PostgreSQL Cluster from a Custom Resource + +The fundamental workflow for interfacing with a PostgreSQL Operator Custom +Resource Definition is for creating a PostgreSQL cluster. There are several +that a PostgreSQL cluster requires to be deployed, including: + +- Secrets + - Information for setting up a pgBackRest repository + - PostgreSQL superuser bootstrap credentials + - PostgreSQL replication user bootstrap credentials + - PostgresQL standard user bootstrap credentials + +Additionally, if you want to add some of the other sidecars, you may need to +create additional secrets. + +The good news is that if you do not provide these objects, the PostgreSQL +Operator will create them for you to get your Postgres cluster up and running! + +The following goes through how to create a PostgreSQL cluster called +`hippo` by creating a new custom resource. + +``` +# this variable is the name of the cluster being created +export pgo_cluster_name=hippo +# this variable is the namespace the cluster is being deployed into +export cluster_namespace=pgo +# this variable is set to the location of your image repository +export cluster_image_prefix=registry.developers.crunchydata.com/crunchydata + +cat <<-EOF > "${pgo_cluster_name}-pgcluster.yaml" +apiVersion: crunchydata.com/v1 +kind: Pgcluster +metadata: + annotations: + current-primary: ${pgo_cluster_name} + labels: + crunchy-pgha-scope: ${pgo_cluster_name} + deployment-name: ${pgo_cluster_name} + name: ${pgo_cluster_name} + pg-cluster: ${pgo_cluster_name} + pgo-version: ${PGO_VERSION} + pgouser: admin + name: ${pgo_cluster_name} + namespace: ${cluster_namespace} +spec: + BackrestStorage: + accessmode: ReadWriteMany + matchLabels: "" + name: "" + size: 1G + storageclass: "" + storagetype: create + supplementalgroups: "" + PrimaryStorage: + accessmode: ReadWriteMany + matchLabels: "" + name: ${pgo_cluster_name} + size: 1G + storageclass: "" + storagetype: create + supplementalgroups: "" + ReplicaStorage: + accessmode: ReadWriteMany + matchLabels: "" + name: "" + size: 1G + storageclass: "" + storagetype: create + supplementalgroups: "" + annotations: {} + ccpimage: crunchy-postgres-ha + ccpimageprefix: ${cluster_image_prefix} + ccpimagetag: centos8-13.1-${PGO_VERSION} + clustername: ${pgo_cluster_name} + database: ${pgo_cluster_name} + exporterport: "9187" + limits: {} + name: ${pgo_cluster_name} + namespace: ${cluster_namespace} + pgDataSource: + restoreFrom: "" + restoreOpts: "" + pgbadgerport: "10000" + pgoimageprefix: ${cluster_image_prefix} + podAntiAffinity: + default: preferred + pgBackRest: preferred + pgBouncer: preferred + port: "5432" + tolerations: [] + user: hippo + userlabels: + pgo-version: ${PGO_VERSION} +EOF + +kubectl apply -f "${pgo_cluster_name}-pgcluster.yaml" +``` + +And that's all! The PostgreSQL Operator will go ahead and create the cluster. + +If you have the PostgreSQL client `psql` installed on your host machine, you can +test connection to the PostgreSQL cluster using the following command: + +``` +# namespace that the cluster is running in +export PGO_OPERATOR_NAMESPACE=pgo +# name of the cluster +export pgo_cluster_name=hippo +# name of the user whose password we want to get +export pgo_cluster_username=hippo + +# get the password of the user and set it to a recognized psql environmental variable +export PGPASSWORD=$(kubectl -n "${PGO_OPERATOR_NAMESPACE}" get secrets \ + "${pgo_cluster_name}-${pgo_cluster_username}-secret" -o "jsonpath={.data['password']}" | base64 -d) + +# set up a port-forward either in a new terminal, or in the same terminal in the background: +kubectl -n pgo port-forward svc/hippo 5432:5432 & + +psql -h localhost -U "${pgo_cluster_username}" "${pgo_cluster_name}" +``` + +### Create a PostgreSQL Cluster the `pgo` Client Once the PostgreSQL Operator is installed in your Kubernetes cluster, you will need to do a few things to use the [PostgreSQL Operator Client][pgo-client]. @@ -118,3 +260,36 @@ pgo version # pgo client version ${PGO_VERSION} # pgo-apiserver version ${PGO_VERSION} ``` + +You can then create a cluster with the `pgo` client as simply as this: + +``` +pgo create cluster -n pgo hippo +``` + +The cluster may take a few moments to provision. You can verify that the cluster is up and running by using the `pgo test` command: + +``` +pgo test cluster -n pgo hippo +``` + +If you have the PostgreSQL client `psql` installed on your host machine, you can +test connection to the PostgreSQL cluster using the following command: + +``` +# namespace that the cluster is running in +export PGO_OPERATOR_NAMESPACE=pgo +# name of the cluster +export pgo_cluster_name=hippo +# name of the user whose password we want to get +export pgo_cluster_username=hippo + +# get the password of the user and set it to a recognized psql environmental variable +export PGPASSWORD=$(kubectl -n "${PGO_OPERATOR_NAMESPACE}" get secrets \ + "${pgo_cluster_name}-${pgo_cluster_username}-secret" -o "jsonpath={.data['password']}" | base64 -d) + +# set up a port-forward either in a new terminal, or in the same terminal in the background: +kubectl -n pgo port-forward svc/hippo 5432:5432 & + +psql -h localhost -U "${pgo_cluster_username}" "${pgo_cluster_name}" +``` diff --git a/installers/olm/postgresoperator.crd.examples.yaml b/installers/olm/postgresoperator.crd.examples.yaml index 49b58fac6c..058e4b56f3 100644 --- a/installers/olm/postgresoperator.crd.examples.yaml +++ b/installers/olm/postgresoperator.crd.examples.yaml @@ -2,23 +2,54 @@ apiVersion: crunchydata.com/v1 kind: Pgcluster metadata: - name: example - labels: { archive: 'false' } + annotations: { current-primary: 'hippo' } + name: hippo + labels: + crunchy-pgha-scope: hippo + deployment-name: hippo + name: hippo + namespace: pgo + pg-cluster: hippo + pgo-version: '${PGO_VERSION}' spec: - name: example - clustername: example + name: hippo + namespace: pgo + clustername: hippo ccpimage: crunchy-postgres-ha ccpimagetag: '${CCP_IMAGE_TAG}' + BackrestStorage: + accessmode: ReadWriteMany + matchLabels: "" + name: "" + size: 5Gi + storageclass: "" + storagetype: dynamic + supplementalgroups: "" PrimaryStorage: - accessmode: ReadWriteOnce - size: 1G - storageclass: standard + accessmode: ReadWriteMany + matchLabels: "" + name: hippo + size: 5Gi + storageclass: "" + storagetype: dynamic + supplementalgroups: "" + ReplicaStorage: + accessmode: ReadWriteMany + matchLabels: "" + name: "" + size: 5Gi + storageclass: "" storagetype: dynamic - database: example + supplementalgroups: "" + database: hippo exporterport: '9187' pgbadgerport: '10000' + podAntiAffinity: + default: preferred port: '5432' - userlabels: { archive: 'false' } + user: hippo + userlabels: + pgo-version: '${PGO_VERSION}' --- apiVersion: crunchydata.com/v1 diff --git a/installers/olm/verify.sh b/installers/olm/verify.sh index f241a4e267..400df960fe 100755 --- a/installers/olm/verify.sh +++ b/installers/olm/verify.sh @@ -20,7 +20,7 @@ if command -v oc >/dev/null; then kubectl() { oc "$@"; } elif ! command -v kubectl >/dev/null; then # Use a version of `kubectl` that matches the Kubernetes server. - eval "kubectl() { kubectl-$( kubectl-1.16 version --output=json | + eval "kubectl() { kubectl-$( kubectl-1.19 version --output=json | jq --raw-output '.serverVersion | .major + "." + .minor')"' "$@"; }' fi From 902e4856b6949dc457db72e85211661614d0b30f Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Thu, 21 Jan 2021 22:59:31 -0500 Subject: [PATCH 162/276] Update Operator architecture diagram This is a more accurate reflection of the current state of the architecture. --- docs/static/Operator-Architecture-wCRDs.png | Bin 182556 -> 87064 bytes docs/static/Operator-Architecture.png | Bin 144647 -> 87064 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/static/Operator-Architecture-wCRDs.png b/docs/static/Operator-Architecture-wCRDs.png index 291cbefef3f14dbebd378f3386d8b95990f3a3f4..8e41f460317a68f87072a2f3208df3a4319223c3 100644 GIT binary patch literal 87064 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

SZs(n8pz72kk6Up-pq{D_q`{Zx10#3#*~P;w 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 bK_HP)1f4I9T<~2ITtVu}+Der#tl#|~4s}hp literal 182556 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

i)!**jWh`A{dSDg?%mFxO#9Ei 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 |6C( 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

^8({= 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

SZs(n8pz72kk6Up-pq{D_q`{Zx10#3#*~P;w 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 bK_HP)1f4I9T<~2ITtVu}+Der#tl#|~4s}hp literal 144647 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

#% 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 Date: Thu, 21 Jan 2021 22:54:56 -0500 Subject: [PATCH 163/276] Bump version 4.6.0 --- Makefile | 2 +- README.md | 4 ++-- bin/push-ccp-to-gcr.sh | 2 +- conf/postgres-operator/pgo.yaml | 4 ++-- docs/config.toml | 2 +- docs/content/releases/4.6.0.md | 6 +++--- examples/create-by-resource/fromcrd.json | 6 +++--- examples/envs.sh | 2 +- examples/helm/README.md | 2 +- examples/helm/postgres/Chart.yaml | 2 +- examples/helm/postgres/templates/pgcluster.yaml | 2 +- examples/helm/postgres/values.yaml | 2 +- examples/kustomize/createcluster/README.md | 16 ++++++++-------- .../kustomize/createcluster/base/pgcluster.yaml | 6 +++--- .../overlay/staging/hippo-rpl1-pgreplica.yaml | 2 +- installers/ansible/README.md | 2 +- installers/ansible/values.yaml | 6 +++--- installers/gcp-marketplace/Makefile | 2 +- installers/gcp-marketplace/README.md | 2 +- installers/gcp-marketplace/values.yaml | 6 +++--- installers/helm/Chart.yaml | 2 +- installers/helm/values.yaml | 6 +++--- installers/kubectl/client-setup.sh | 2 +- installers/kubectl/postgres-operator-ocp311.yml | 8 ++++---- installers/kubectl/postgres-operator.yml | 8 ++++---- installers/metrics/ansible/README.md | 2 +- installers/metrics/helm/Chart.yaml | 2 +- installers/metrics/helm/helm_template.yaml | 2 +- installers/metrics/helm/values.yaml | 2 +- .../kubectl/postgres-operator-metrics-ocp311.yml | 2 +- .../kubectl/postgres-operator-metrics.yml | 2 +- installers/olm/Makefile | 2 +- pkg/apis/crunchydata.com/v1/doc.go | 8 ++++---- pkg/apiservermsgs/common.go | 2 +- redhat/atomic/help.1 | 2 +- redhat/atomic/help.md | 2 +- 36 files changed, 66 insertions(+), 66 deletions(-) diff --git a/Makefile b/Makefile index 86b7166458..a219cc2785 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ PGOROOT ?= $(CURDIR) PGO_BASEOS ?= centos8 PGO_IMAGE_PREFIX ?= crunchydata PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) -PGO_VERSION ?= 4.6.0-rc.1 +PGO_VERSION ?= 4.6.0 PGO_PG_VERSION ?= 13 PGO_PG_FULLVERSION ?= 13.1 PGO_BACKREST_VERSION ?= 2.31 diff --git a/README.md b/README.md index 8051906430..0ec169ebf5 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ The Crunchy PostgreSQL Operator makes it easy to get your own PostgreSQL-as-a-Se [monitoring]: https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/monitoring/ [multiple-cluster]: https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/high-availability/multi-cluster-kubernetes/ [pgo-create-cluster]: https://access.crunchydata.com/documentation/postgres-operator/latest/pgo-client/reference/pgo_create_cluster/ -[pgo-task-tls]: https://access.crunchydata.com/documentation/postgres-operator/latest/pgo-client/common-tasks/#enable-tls +[pgo-task-tls]: https://access.crunchydata.com/documentation/postgres-operator/latest/tutorial/tls/ [provisioning]: https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/provisioning/ [k8s-anti-affinity]: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#inter-pod-affinity-and-anti-affinity @@ -167,7 +167,7 @@ Based on your storage settings in your Kubernetes environment, you may be able t ```shell kubectl create namespace pgo -kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.5.1/installers/kubectl/postgres-operator.yml +kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.0/installers/kubectl/postgres-operator.yml ``` Otherwise, we highly recommend following the instructions from our [Quickstart](https://access.crunchydata.com/documentation/postgres-operator/latest/quickstart/). diff --git a/bin/push-ccp-to-gcr.sh b/bin/push-ccp-to-gcr.sh index 28ecdadeb6..638fb13ab5 100755 --- a/bin/push-ccp-to-gcr.sh +++ b/bin/push-ccp-to-gcr.sh @@ -16,7 +16,7 @@ GCR_IMAGE_PREFIX=gcr.io/crunchy-dev-test CCP_IMAGE_PREFIX=crunchydata -CCP_IMAGE_TAG=centos8-13.1-4.6.0-rc.1 +CCP_IMAGE_TAG=centos8-13.1-4.6.0 IMAGES=( crunchy-prometheus diff --git a/conf/postgres-operator/pgo.yaml b/conf/postgres-operator/pgo.yaml index e95da5d9d6..0b78876463 100644 --- a/conf/postgres-operator/pgo.yaml +++ b/conf/postgres-operator/pgo.yaml @@ -2,7 +2,7 @@ Cluster: CCPImagePrefix: registry.developers.crunchydata.com/crunchydata Metrics: false Badger: false - CCPImageTag: centos8-13.1-4.6.0-rc.1 + CCPImageTag: centos8-13.1-4.6.0 Port: 5432 PGBadgerPort: 10000 ExporterPort: 9187 @@ -81,4 +81,4 @@ Storage: Pgo: Audit: false PGOImagePrefix: registry.developers.crunchydata.com/crunchydata - PGOImageTag: centos8-4.6.0-rc.1 + PGOImageTag: centos8-4.6.0 diff --git a/docs/config.toml b/docs/config.toml index 8d34478847..849b908b2a 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -25,7 +25,7 @@ disableNavChevron = false # set true to hide next/prev chevron, default is false highlightClientSide = false # set true to use highlight.pack.js instead of the default hugo chroma highlighter menushortcutsnewtab = true # set true to open shortcuts links to a new tab/window enableGitInfo = true -operatorVersion = "4.6.0-rc.1" +operatorVersion = "4.6.0" postgresVersion = "13.1" postgresVersion13 = "13.1" postgresVersion12 = "13.1" diff --git a/docs/content/releases/4.6.0.md b/docs/content/releases/4.6.0.md index 499101e086..0c1248d261 100644 --- a/docs/content/releases/4.6.0.md +++ b/docs/content/releases/4.6.0.md @@ -5,18 +5,18 @@ draft: false weight: 60 --- -Crunchy Data announces the release of the PostgreSQL Operator 4.6.0 on January DD, 2021. You can get started with the PostgreSQL Operator with the following commands: +Crunchy Data announces the release of the PostgreSQL Operator 4.6.0 on January 22, 2021. You can get started with the PostgreSQL Operator with the following commands: ``` kubectl create namespace pgo -kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.0-beta.1/installers/kubectl/postgres-operator.yml +kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.0-rc.1/installers/kubectl/postgres-operator.yml ``` The PostgreSQL Operator is released in conjunction with the [Crunchy Container Suite](https://github.com/CrunchyData/crunchy-containers/). The PostgreSQL Operator 4.6.0 release includes the following software versions upgrades: -- [pgBackRest](https://pgbackrest.org/) is now at version 2.31. +- [pgBackRest](https://pgbackrest.org/) is now at version 2.31 - [pgnodemx](https://github.com/CrunchyData/pgnodemx) is now at version 1.0.3 - [Patroni](https://patroni.readthedocs.io/) is now at version 2.0.1 - [pgBadger](https://github.com/darold/pgbadger) is now at 11.4 diff --git a/examples/create-by-resource/fromcrd.json b/examples/create-by-resource/fromcrd.json index 2876f4c248..9483cf2adb 100644 --- a/examples/create-by-resource/fromcrd.json +++ b/examples/create-by-resource/fromcrd.json @@ -10,7 +10,7 @@ "deployment-name": "fromcrd", "name": "fromcrd", "pg-cluster": "fromcrd", - "pgo-version": "4.6.0-rc.1", + "pgo-version": "4.6.0", "pgouser": "pgoadmin" }, "name": "fromcrd", @@ -45,7 +45,7 @@ "supplementalgroups": "" }, "ccpimage": "crunchy-postgres-ha", - "ccpimagetag": "centos8-13.1-4.6.0-rc.1", + "ccpimagetag": "centos8-13.1-4.6.0", "clustername": "fromcrd", "database": "userdb", "exporterport": "9187", @@ -60,7 +60,7 @@ "port": "5432", "user": "testuser", "userlabels": { - "pgo-version": "4.6.0-rc.1" + "pgo-version": "4.6.0" } } } diff --git a/examples/envs.sh b/examples/envs.sh index c26dc96310..5788af2eb3 100644 --- a/examples/envs.sh +++ b/examples/envs.sh @@ -20,7 +20,7 @@ export PGO_CONF_DIR=$PGOROOT/installers/ansible/roles/pgo-operator/files # the version of the Operator you run is set by these vars export PGO_IMAGE_PREFIX=registry.developers.crunchydata.com/crunchydata export PGO_BASEOS=centos8 -export PGO_VERSION=4.6.0-rc.1 +export PGO_VERSION=4.6.0 export PGO_IMAGE_TAG=$PGO_BASEOS-$PGO_VERSION # for setting the pgo apiserver port, disabling TLS or not verifying TLS diff --git a/examples/helm/README.md b/examples/helm/README.md index 75d7cf2123..c99a3e58d0 100644 --- a/examples/helm/README.md +++ b/examples/helm/README.md @@ -64,7 +64,7 @@ The following values can also be set: - `ha`: Whether or not to deploy a high availability PostgreSQL cluster. Can be either `true` or `false`, defaults to `false`. - `imagePrefix`: The prefix of the container images to use for this PostgreSQL cluster. Default to `registry.developers.crunchydata.com/crunchydata`. - `image`: The name of the container image to use for the PostgreSQL cluster. Defaults to `crunchy-postgres-ha`. -- `imageTag`: The container image tag to use. Defaults to `centos8-13.1-4.6.0-rc.1`. +- `imageTag`: The container image tag to use. Defaults to `centos8-13.1-4.6.0`. - `memory`: The memory limit for the PostgreSQL cluster. Follows standard Kubernetes formatting. - `monitoring`: Whether or not to enable monitoring / metrics collection for this PostgreSQL instance. Can either be `true` or `false`, defaults to `false`. diff --git a/examples/helm/postgres/Chart.yaml b/examples/helm/postgres/Chart.yaml index fe2a16284c..60501fc9a7 100644 --- a/examples/helm/postgres/Chart.yaml +++ b/examples/helm/postgres/Chart.yaml @@ -20,4 +20,4 @@ version: 0.2.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. -appVersion: 4.6.0-rc.1 +appVersion: 4.6.0 diff --git a/examples/helm/postgres/templates/pgcluster.yaml b/examples/helm/postgres/templates/pgcluster.yaml index 83c3519af9..c4d71c51c2 100644 --- a/examples/helm/postgres/templates/pgcluster.yaml +++ b/examples/helm/postgres/templates/pgcluster.yaml @@ -28,7 +28,7 @@ spec: storagetype: dynamic ccpimage: {{ .Values.image | default "crunchy-postgres-ha" | quote }} ccpimageprefix: {{ .Values.imagePrefix | default "registry.developers.crunchydata.com/crunchydata" | quote }} - ccpimagetag: {{ .Values.imageTag | default "centos8-13.1-4.6.0-rc.1" | quote }} + ccpimagetag: {{ .Values.imageTag | default "centos8-13.1-4.6.0" | quote }} clustername: {{ .Values.name | quote }} database: {{ .Values.name | quote }} {{- if .Values.monitoring }} diff --git a/examples/helm/postgres/values.yaml b/examples/helm/postgres/values.yaml index 0af1278336..ac247df8db 100644 --- a/examples/helm/postgres/values.yaml +++ b/examples/helm/postgres/values.yaml @@ -10,5 +10,5 @@ password: W4tch0ut4hippo$ # ha: true # imagePrefix: registry.developers.crunchydata.com/crunchydata # image: crunchy-postgres-ha -# imageTag: centos8-13.1-4.6.0-rc.1 +# imageTag: centos8-13.1-4.6.0 # memory: 1Gi diff --git a/examples/kustomize/createcluster/README.md b/examples/kustomize/createcluster/README.md index 3fe3244bb5..0b35a3ae22 100644 --- a/examples/kustomize/createcluster/README.md +++ b/examples/kustomize/createcluster/README.md @@ -44,13 +44,13 @@ pgo show cluster hippo -n pgo ``` You will see something like this if successful: ``` -cluster : hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-rc.1) +cluster : hippo (crunchy-postgres-ha:centos8-13.1-4.6.0) pod : hippo-8fb6bd96-j87wq (Running) on gke-xxxx-default-pool-38e946bd-257w (1/1) (primary) pvc: hippo (1Gi) deployment : hippo deployment : hippo-backrest-shared-repo service : hippo - ClusterIP (10.0.56.86) - Ports (2022/TCP, 5432/TCP) - labels : pgo-version=4.6.0-rc.1 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.0 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata ``` Feel free to run other pgo cli commands on the hippo cluster @@ -79,7 +79,7 @@ pgo show cluster dev-hippo -n pgo ``` You will see something like this if successful: ``` -cluster : dev-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-rc.1) +cluster : dev-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0) pod : dev-hippo-588d4cb746-bwrxb (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: dev-hippo (1Gi) deployment : dev-hippo @@ -87,7 +87,7 @@ cluster : dev-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-rc.1) deployment : dev-hippo-pgbouncer service : dev-hippo - ClusterIP (10.0.62.87) - Ports (2022/TCP, 5432/TCP) service : dev-hippo-pgbouncer - ClusterIP (10.0.48.120) - Ports (5432/TCP) - labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.6.0-rc.1 pgouser=admin + labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.6.0 pgouser=admin ``` #### staging The staging overlay will deploy a crunchy postgreSQL cluster with 2 replica's with annotations added @@ -113,7 +113,7 @@ pgo show cluster staging-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : staging-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-rc.1) +cluster : staging-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0) pod : staging-hippo-85cf6dcb65-9h748 (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: staging-hippo (1Gi) pod : staging-hippo-lnxw-cf47d8c8b-6r4wn (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (replica) @@ -128,7 +128,7 @@ cluster : staging-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-rc.1) service : staging-hippo-replica - ClusterIP (10.0.56.57) - Ports (2022/TCP, 5432/TCP) pgreplica : staging-hippo-lnxw pgreplica : staging-hippo-rpl1 - labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.6.0-rc.1 pgouser=admin vendor=crunchydata + labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.6.0 pgouser=admin vendor=crunchydata ``` #### production @@ -154,7 +154,7 @@ pgo show cluster prod-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : prod-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-rc.1) +cluster : prod-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0) pod : prod-hippo-5d6dd46497-rr67c (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (primary) pvc: prod-hippo (1Gi) pod : prod-hippo-flty-84d97c8769-2pzbh (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (replica) @@ -165,7 +165,7 @@ cluster : prod-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0-rc.1) service : prod-hippo - ClusterIP (10.0.56.18) - Ports (2022/TCP, 5432/TCP) service : prod-hippo-replica - ClusterIP (10.0.56.101) - Ports (2022/TCP, 5432/TCP) pgreplica : prod-hippo-flty - labels : pgo-version=4.6.0-rc.1 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.0 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata ``` ### Delete the clusters To delete the clusters run the following pgo cli commands diff --git a/examples/kustomize/createcluster/base/pgcluster.yaml b/examples/kustomize/createcluster/base/pgcluster.yaml index 53c874c670..ae2137b1f1 100644 --- a/examples/kustomize/createcluster/base/pgcluster.yaml +++ b/examples/kustomize/createcluster/base/pgcluster.yaml @@ -10,7 +10,7 @@ metadata: deployment-name: hippo name: hippo pg-cluster: hippo - pgo-version: 4.6.0-rc.1 + pgo-version: 4.6.0 pgouser: admin name: hippo namespace: pgo @@ -42,7 +42,7 @@ spec: annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: registry.developers.crunchydata.com/crunchydata - ccpimagetag: centos8-13.1-4.6.0-rc.1 + ccpimagetag: centos8-13.1-4.6.0 clustername: hippo customconfig: "" database: hippo @@ -63,4 +63,4 @@ spec: port: "5432" user: hippo userlabels: - pgo-version: 4.6.0-rc.1 + pgo-version: 4.6.0 diff --git a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml index f5b204e760..b9904e2cb8 100644 --- a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml +++ b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml @@ -20,4 +20,4 @@ spec: storagetype: dynamic supplementalgroups: "" userlabels: - pgo-version: 4.6.0-rc.1 + pgo-version: 4.6.0 diff --git a/installers/ansible/README.md b/installers/ansible/README.md index 5c63176c50..a7be44259a 100644 --- a/installers/ansible/README.md +++ b/installers/ansible/README.md @@ -4,7 +4,7 @@ Crunchy Data

-Latest Release: 4.6.0-rc.1 +Latest Release: 4.6.0 ## General diff --git a/installers/ansible/values.yaml b/installers/ansible/values.yaml index 03cf3f10b4..d108a194f9 100644 --- a/installers/ansible/values.yaml +++ b/installers/ansible/values.yaml @@ -17,7 +17,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.1-4.6.0-rc.1" +ccp_image_tag: "centos8-13.1-4.6.0" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -50,14 +50,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.0-rc.1" +pgo_client_version: "4.6.0" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos8-4.6.0-rc.1" +pgo_image_tag: "centos8-4.6.0" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/gcp-marketplace/Makefile b/installers/gcp-marketplace/Makefile index afb1b6eba1..21c0a54376 100644 --- a/installers/gcp-marketplace/Makefile +++ b/installers/gcp-marketplace/Makefile @@ -6,7 +6,7 @@ MARKETPLACE_TOOLS ?= gcr.io/cloud-marketplace-tools/k8s/dev:$(MARKETPLACE_VERSIO MARKETPLACE_VERSION ?= 0.9.4 KUBECONFIG ?= $(HOME)/.kube/config PARAMETERS ?= {} -PGO_VERSION ?= 4.6.0-rc.1 +PGO_VERSION ?= 4.6.0 IMAGE_BUILD_ARGS = --build-arg MARKETPLACE_VERSION='$(MARKETPLACE_VERSION)' \ --build-arg PGO_VERSION='$(PGO_VERSION)' diff --git a/installers/gcp-marketplace/README.md b/installers/gcp-marketplace/README.md index b9d6a8356f..b079936d64 100644 --- a/installers/gcp-marketplace/README.md +++ b/installers/gcp-marketplace/README.md @@ -59,7 +59,7 @@ Google Cloud Marketplace. ```shell IMAGE_REPOSITORY=gcr.io/crunchydata-public/postgres-operator - export PGO_VERSION=4.6.0-rc.1 + export PGO_VERSION=4.6.0 export INSTALLER_IMAGE=${IMAGE_REPOSITORY}/deployer:${PGO_VERSION} export OPERATOR_IMAGE=${IMAGE_REPOSITORY}:${PGO_VERSION} export OPERATOR_IMAGE_API=${IMAGE_REPOSITORY}/pgo-apiserver:${PGO_VERSION} diff --git a/installers/gcp-marketplace/values.yaml b/installers/gcp-marketplace/values.yaml index 19661b4702..3b44d04e88 100644 --- a/installers/gcp-marketplace/values.yaml +++ b/installers/gcp-marketplace/values.yaml @@ -10,7 +10,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.1-4.6.0-rc.1" +ccp_image_tag: "centos8-13.1-4.6.0" create_rbac: "true" db_name: "" db_password_age_days: "0" @@ -32,9 +32,9 @@ pgo_admin_role_name: "pgoadmin" pgo_admin_username: "admin" pgo_client_container_install: "false" pgo_client_install: 'false' -pgo_client_version: "4.6.0-rc.1" +pgo_client_version: "4.6.0" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.0-rc.1" +pgo_image_tag: "centos8-4.6.0" pgo_installation_name: '${OPERATOR_NAME}' pgo_operator_namespace: '${OPERATOR_NAMESPACE}' scheduler_timeout: "3600" diff --git a/installers/helm/Chart.yaml b/installers/helm/Chart.yaml index 6108bd738e..fcb7fba745 100644 --- a/installers/helm/Chart.yaml +++ b/installers/helm/Chart.yaml @@ -3,7 +3,7 @@ name: postgres-operator description: Crunchy PostgreSQL Operator Helm chart for Kubernetes type: application version: 0.2.0 -appVersion: 4.6.0-rc.1 +appVersion: 4.6.0 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/crunchy_logo.png keywords: diff --git a/installers/helm/values.yaml b/installers/helm/values.yaml index 563cbc94da..33f85fed19 100644 --- a/installers/helm/values.yaml +++ b/installers/helm/values.yaml @@ -37,7 +37,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.1-4.6.0-rc.1" +ccp_image_tag: "centos8-13.1-4.6.0" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -70,14 +70,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.0-rc.1" +pgo_client_version: "4.6.0" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos8-4.6.0-rc.1" +pgo_image_tag: "centos8-4.6.0" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/kubectl/client-setup.sh b/installers/kubectl/client-setup.sh index 020bfa7fc0..4f8c216a64 100755 --- a/installers/kubectl/client-setup.sh +++ b/installers/kubectl/client-setup.sh @@ -14,7 +14,7 @@ # This script should be run after the operator has been deployed PGO_OPERATOR_NAMESPACE="${PGO_OPERATOR_NAMESPACE:-pgo}" PGO_USER_ADMIN="${PGO_USER_ADMIN:-pgouser-admin}" -PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.0-rc.1}" +PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.0}" PGO_CLIENT_URL="https://github.com/CrunchyData/postgres-operator/releases/download/${PGO_CLIENT_VERSION}" PGO_CMD="${PGO_CMD-kubectl}" diff --git a/installers/kubectl/postgres-operator-ocp311.yml b/installers/kubectl/postgres-operator-ocp311.yml index a35230a1a0..df05020bc2 100644 --- a/installers/kubectl/postgres-operator-ocp311.yml +++ b/installers/kubectl/postgres-operator-ocp311.yml @@ -44,7 +44,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos8-13.1-4.6.0-rc.1" + ccp_image_tag: "centos8-13.1-4.6.0" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -77,14 +77,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.0-rc.1" + pgo_client_version: "4.6.0" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos8-4.6.0-rc.1" + pgo_image_tag: "centos8-4.6.0" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -161,7 +161,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-rc.1 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/kubectl/postgres-operator.yml b/installers/kubectl/postgres-operator.yml index f890a89055..ca57f5c380 100644 --- a/installers/kubectl/postgres-operator.yml +++ b/installers/kubectl/postgres-operator.yml @@ -139,7 +139,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos8-13.1-4.6.0-rc.1" + ccp_image_tag: "centos8-13.1-4.6.0" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -172,14 +172,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.0-rc.1" + pgo_client_version: "4.6.0" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos8-4.6.0-rc.1" + pgo_image_tag: "centos8-4.6.0" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -269,7 +269,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-rc.1 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/ansible/README.md b/installers/metrics/ansible/README.md index 57dbdb8b6a..5b2e93ad52 100644 --- a/installers/metrics/ansible/README.md +++ b/installers/metrics/ansible/README.md @@ -4,7 +4,7 @@ Crunchy Data

-Latest Release: 4.6.0-rc.1 +Latest Release: 4.6.0 ## General diff --git a/installers/metrics/helm/Chart.yaml b/installers/metrics/helm/Chart.yaml index 66c4aeecf9..8155045d2e 100644 --- a/installers/metrics/helm/Chart.yaml +++ b/installers/metrics/helm/Chart.yaml @@ -3,6 +3,6 @@ name: postgres-operator-monitoring description: Install for Crunchy PostgreSQL Operator Monitoring type: application version: 0.2.0 -appVersion: 4.6.0-rc.1 +appVersion: 4.6.0 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/crunchy_logo.png diff --git a/installers/metrics/helm/helm_template.yaml b/installers/metrics/helm/helm_template.yaml index 12535de89a..718a29901f 100644 --- a/installers/metrics/helm/helm_template.yaml +++ b/installers/metrics/helm/helm_template.yaml @@ -20,5 +20,5 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.0-rc.1" +pgo_image_tag: "centos8-4.6.0" diff --git a/installers/metrics/helm/values.yaml b/installers/metrics/helm/values.yaml index 88e9f42582..f7a9300d19 100644 --- a/installers/metrics/helm/values.yaml +++ b/installers/metrics/helm/values.yaml @@ -20,7 +20,7 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.0-rc.1" +pgo_image_tag: "centos8-4.6.0" # ===================== # Configuration Options diff --git a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml index 026ad5d04a..edb7a514b1 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml @@ -96,7 +96,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-rc.1 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/kubectl/postgres-operator-metrics.yml b/installers/metrics/kubectl/postgres-operator-metrics.yml index c999653f38..382b0a7259 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics.yml @@ -165,7 +165,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0-rc.1 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/olm/Makefile b/installers/olm/Makefile index e3482a7eef..7d513b7452 100644 --- a/installers/olm/Makefile +++ b/installers/olm/Makefile @@ -11,7 +11,7 @@ OLM_TOOLS ?= registry.localhost:5000/postgres-operator-olm-tools:$(OLM_SDK_VERSI OLM_VERSION ?= 0.15.1 PGO_BASEOS ?= centos8 PGO_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -PGO_VERSION ?= 4.6.0-rc.1 +PGO_VERSION ?= 4.6.0 PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) CCP_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(PGO_VERSION) CCP_POSTGIS_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(CCP_POSTGIS_VERSION)-$(PGO_VERSION) diff --git a/pkg/apis/crunchydata.com/v1/doc.go b/pkg/apis/crunchydata.com/v1/doc.go index 8c7a21173a..c02da94ccc 100644 --- a/pkg/apis/crunchydata.com/v1/doc.go +++ b/pkg/apis/crunchydata.com/v1/doc.go @@ -53,7 +53,7 @@ cluster. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.0-rc.1", + '{"ClientVersion":"4.6.0", "Namespace":"pgouser1", "Name":"mycluster", $PGO_APISERVER_URL/clusters @@ -72,7 +72,7 @@ show all of the clusters that are in the given namespace. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.0-rc.1", + '{"ClientVersion":"4.6.0", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/showclusters @@ -82,7 +82,7 @@ $PGO_APISERVER_URL/showclusters curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.0-rc.1", + '{"ClientVersion":"4.6.0", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/clustersdelete @@ -90,7 +90,7 @@ $PGO_APISERVER_URL/clustersdelete Schemes: http, https BasePath: / - Version: 4.6.0-rc.1 + Version: 4.6.0 License: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0 Contact: Crunchy Data https://www.crunchydata.com/ diff --git a/pkg/apiservermsgs/common.go b/pkg/apiservermsgs/common.go index 3d24393610..b4acfbd6ba 100644 --- a/pkg/apiservermsgs/common.go +++ b/pkg/apiservermsgs/common.go @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -const PGO_VERSION = "4.6.0-rc.1" +const PGO_VERSION = "4.6.0" // Ok status const Ok = "ok" diff --git a/redhat/atomic/help.1 b/redhat/atomic/help.1 index d94801c5cc..6f16a1c76d 100644 --- a/redhat/atomic/help.1 +++ b/redhat/atomic/help.1 @@ -56,4 +56,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa \fB\fCRelease=\fR .PP -The specific release number of the container. For example, Release="4.6.0-rc.1" +The specific release number of the container. For example, Release="4.6.0" diff --git a/redhat/atomic/help.md b/redhat/atomic/help.md index f0889e1cdf..b156931e07 100644 --- a/redhat/atomic/help.md +++ b/redhat/atomic/help.md @@ -45,4 +45,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa `Release=` -The specific release number of the container. For example, Release="4.6.0-rc.1" +The specific release number of the container. For example, Release="4.6.0" From a22c3a8277a49a4cdfc2c949d9a2a331f5f3c55c Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Thu, 21 Jan 2021 23:16:32 -0500 Subject: [PATCH 164/276] Fix Kustomize example The commit author was previously overly aggressive in cleaning up methods in the base. --- examples/kustomize/createcluster/base/pgcluster.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/kustomize/createcluster/base/pgcluster.yaml b/examples/kustomize/createcluster/base/pgcluster.yaml index ae2137b1f1..28fbf7f49d 100644 --- a/examples/kustomize/createcluster/base/pgcluster.yaml +++ b/examples/kustomize/createcluster/base/pgcluster.yaml @@ -39,7 +39,11 @@ spec: storageclass: "" storagetype: dynamic supplementalgroups: "" - annotations: {} + annotations: + global: {} + backrest: {} + pgBouncer: {} + postgres: {} ccpimage: crunchy-postgres-ha ccpimageprefix: registry.developers.crunchydata.com/crunchydata ccpimagetag: centos8-13.1-4.6.0 @@ -50,6 +54,8 @@ spec: limits: {} name: hippo namespace: pgo + pgBouncer: + resources: {} pgDataSource: restoreFrom: "" restoreOpts: "" From 9fb2030e8ef435b58d4de1eabebda51e4fdcbd5c Mon Sep 17 00:00:00 2001 From: andrewlecuyer <43458182+andrewlecuyer@users.noreply.github.com> Date: Fri, 22 Jan 2021 08:14:00 -0600 Subject: [PATCH 165/276] Update Replica Init Logic The operator will now only initialize replica creation prior to stanza creation if the cluster has s3-only enabled. Otherwise replicas will be created following stanza-creation. Additionally, the InitializeReplicaCreation() function (as used to trigger the creation of replicas via pgreplica custom resources) now uses a Patch() operation instead of an Update() operation. This is to prevent potential conflicts when updating a pgreplica. And finally, error logging has been added to add proper errors logs during standby cluster initialization. --- internal/controller/controllerutil.go | 19 ++++++++------- internal/controller/job/backresthandler.go | 28 ++++++++++++++++------ internal/controller/pod/inithandler.go | 22 ++++++++++------- 3 files changed, 46 insertions(+), 23 deletions(-) diff --git a/internal/controller/controllerutil.go b/internal/controller/controllerutil.go index 1db944cf3b..f196d84be9 100644 --- a/internal/controller/controllerutil.go +++ b/internal/controller/controllerutil.go @@ -21,6 +21,7 @@ import ( "errors" "github.com/crunchydata/postgres-operator/internal/config" + "github.com/crunchydata/postgres-operator/internal/kubeapi" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" pgo "github.com/crunchydata/postgres-operator/pkg/generated/clientset/versioned" log "github.com/sirupsen/logrus" @@ -62,18 +63,20 @@ func InitializeReplicaCreation(clientset pgo.Interface, clusterName, log.Error(err) return err } - for i := range pgreplicaList.Items { - pgreplica := &pgreplicaList.Items[i] - if pgreplica.Annotations == nil { - pgreplica.Annotations = make(map[string]string) + for i := range pgreplicaList.Items { + patch, err := kubeapi.NewMergePatch(). + Add("metadata", "annotations")(map[string]string{ + config.ANNOTATION_PGHA_BOOTSTRAP_REPLICA: "true", + }).Bytes() + if err != nil { + log.Error(err) } - pgreplica.Annotations[config.ANNOTATION_PGHA_BOOTSTRAP_REPLICA] = "true" - - if _, err = clientset.CrunchydataV1().Pgreplicas(namespace).Update(ctx, pgreplica, metav1.UpdateOptions{}); err != nil { + if _, err := clientset.CrunchydataV1().Pgreplicas(namespace). + Patch(ctx, pgreplicaList.Items[i].GetName(), types.MergePatchType, patch, + metav1.PatchOptions{}); err != nil { log.Error(err) - return err } } return nil diff --git a/internal/controller/job/backresthandler.go b/internal/controller/job/backresthandler.go index 43f6173311..a55c47f931 100644 --- a/internal/controller/job/backresthandler.go +++ b/internal/controller/job/backresthandler.go @@ -84,12 +84,18 @@ func (c *Controller) handleBackrestBackupUpdate(job *apiv1.Job) error { if labels[config.LABEL_PGHA_BACKUP_TYPE] == crv1.BackupTypeBootstrap { log.Debugf("jobController onUpdate initial backup complete") - _ = controller.SetClusterInitializedStatus(c.Client, labels[config.LABEL_PG_CLUSTER], - job.ObjectMeta.Namespace) + if err := controller.SetClusterInitializedStatus(c.Client, labels[config.LABEL_PG_CLUSTER], + job.ObjectMeta.Namespace); err != nil { + log.Error(err) + return err + } - // now initialize the creation of any replica - _ = controller.InitializeReplicaCreation(c.Client, labels[config.LABEL_PG_CLUSTER], - job.ObjectMeta.Namespace) + // now initialize the creation of any replicas + if err := controller.InitializeReplicaCreation(c.Client, labels[config.LABEL_PG_CLUSTER], + job.ObjectMeta.Namespace); err != nil { + log.Error(err) + return err + } } else if labels[config.LABEL_PGHA_BACKUP_TYPE] == crv1.BackupTypeFailover { if err := operator.RemovePrimaryOnRoleChangeTag(c.Client, c.Client.Config, @@ -138,10 +144,18 @@ func (c *Controller) handleBackrestStanzaCreateUpdate(job *apiv1.Job) error { if cluster.Spec.Standby { log.Debugf("job Controller: standby cluster %s will now be set to an initialized "+ "status", clusterName) - _ = controller.SetClusterInitializedStatus(c.Client, clusterName, namespace) + if err := controller.SetClusterInitializedStatus(c.Client, clusterName, + namespace); err != nil { + log.Error(err) + return err + } // now initialize the creation of any replica - _ = controller.InitializeReplicaCreation(c.Client, clusterName, namespace) + if err := controller.InitializeReplicaCreation(c.Client, clusterName, + namespace); err != nil { + log.Error(err) + return err + } return nil } diff --git a/internal/controller/pod/inithandler.go b/internal/controller/pod/inithandler.go index 8379d4859f..d5b3b10bca 100644 --- a/internal/controller/pod/inithandler.go +++ b/internal/controller/pod/inithandler.go @@ -208,15 +208,21 @@ func (c *Controller) handleStandbyInit(cluster *crv1.Pgcluster) error { } backrestoperator.StanzaCreate(namespace, clusterName, c.Client) } else { - _ = controller.SetClusterInitializedStatus(c.Client, clusterName, namespace) - } + if err := controller.SetClusterInitializedStatus(c.Client, clusterName, + namespace); err != nil { + log.Error(err) + } - // If a standby cluster initialize the creation of any replicas. Replicas - // can be initialized right away, i.e. there is no dependency on - // stanza-creation and/or the creation of any backups, since the replicas - // will be generated from the pgBackRest repository of an external PostgreSQL - // database (which should already exist). - _ = controller.InitializeReplicaCreation(c.Client, clusterName, namespace) + // If a standby cluster with s3 only initialize the creation of any replicas. Replicas + // can be initialized right away, i.e. there is no dependency on + // stanza-creation and/or the creation of any backups, since the replicas + // will be generated from the pgBackRest repository of an external PostgreSQL + // database (which should already exist). + if err := controller.InitializeReplicaCreation(c.Client, clusterName, + namespace); err != nil { + log.Error(err) + } + } // if this is a pgbouncer enabled cluster, add a pgbouncer // Note: we only warn if we cannot create the pgBouncer, so eecution can From e2aa57b26b320d533c76a775f6bc06450960bed5 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Fri, 22 Jan 2021 11:13:32 -0500 Subject: [PATCH 166/276] Fix reference in the 4.6.0 release notes This points to the 4.6.0 mainfest for installation, not RC1. --- docs/content/releases/4.6.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/releases/4.6.0.md b/docs/content/releases/4.6.0.md index 0c1248d261..d73caa8eea 100644 --- a/docs/content/releases/4.6.0.md +++ b/docs/content/releases/4.6.0.md @@ -9,7 +9,7 @@ Crunchy Data announces the release of the PostgreSQL Operator 4.6.0 on January 2 ``` kubectl create namespace pgo -kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.0-rc.1/installers/kubectl/postgres-operator.yml +kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.0/installers/kubectl/postgres-operator.yml ``` The PostgreSQL Operator is released in conjunction with the [Crunchy Container Suite](https://github.com/CrunchyData/crunchy-containers/). From 657bf2a708675714644be4020c6255c5802465bd Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Fri, 29 Jan 2021 13:13:43 -0500 Subject: [PATCH 167/276] Add support for the pgBackRest `--compress-type` flag Selecting the compression type for pgBackRest is supported across recent Postgres Operator releases, but the CLI was not allowing for this flag to be passed through. This allows for the selection of pgBackRest compression type amongst the allowable methods in the container, which are none, bz2, gz, and lz4. Currently zst does not ship within the container. Issue: [ch10287] --- .../backupoptions/backupoptionsutil.go | 7 ++++ .../backupoptions/backupoptionsutil_test.go | 40 +++++++++++++++++++ .../backupoptions/pgbackrestoptions.go | 6 +++ 3 files changed, 53 insertions(+) create mode 100644 internal/apiserver/backupoptions/backupoptionsutil_test.go diff --git a/internal/apiserver/backupoptions/backupoptionsutil.go b/internal/apiserver/backupoptions/backupoptionsutil.go index 75b922b957..0ce5e39be9 100644 --- a/internal/apiserver/backupoptions/backupoptionsutil.go +++ b/internal/apiserver/backupoptions/backupoptionsutil.go @@ -164,6 +164,13 @@ func isValidCompressLevel(compressLevel int) bool { } } +// isValidCompressType checks that the compression type passed in matches one +// of the ones supported by pgBackRest. However, it presently does not support +// `zst` +func isValidCompressType(compressType string) bool { + return compressType == "gz" || compressType == "bz2" || compressType == "lz4" || compressType == "none" +} + // isValidRetentionRange validates that pgBackrest Full, Diff or Archive // retention option value is set within the allowable range. // allowed: 1-9999999 diff --git a/internal/apiserver/backupoptions/backupoptionsutil_test.go b/internal/apiserver/backupoptions/backupoptionsutil_test.go new file mode 100644 index 0000000000..44048f15d8 --- /dev/null +++ b/internal/apiserver/backupoptions/backupoptionsutil_test.go @@ -0,0 +1,40 @@ +package backupoptions + +/* +Copyright 2021 Crunchy Data Solutions, Inc. +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. +*/ + +import "testing" + +func TestIsValidCompressType(t *testing.T) { + tests := []struct { + compressType string + expected bool + }{ + {compressType: "bz2", expected: true}, + {compressType: "gz", expected: true}, + {compressType: "none", expected: true}, + {compressType: "lz4", expected: true}, + {compressType: "zst", expected: false}, + {compressType: "bogus", expected: false}, + } + + for _, test := range tests { + t.Run(test.compressType, func(t *testing.T) { + if isValidCompressType(test.compressType) != test.expected { + t.Fatalf("expected %q to be %t", test.compressType, test.expected) + } + }) + } +} diff --git a/internal/apiserver/backupoptions/pgbackrestoptions.go b/internal/apiserver/backupoptions/pgbackrestoptions.go index de5cbcb1b3..e4c9e6347a 100644 --- a/internal/apiserver/backupoptions/pgbackrestoptions.go +++ b/internal/apiserver/backupoptions/pgbackrestoptions.go @@ -82,6 +82,7 @@ type pgBackRestBackupOptions struct { NoCompress bool `flag:"no-compress"` CompressLevel int `flag:"compress-level"` CompressLevelNetwork int `flag:"compress-level-network"` + CompressType string `flag:"compress-type"` DBTimeout int `flag:"db-timeout"` Delta bool `flag:"no-delta"` ProcessMax int `flag:"process-max"` @@ -145,6 +146,11 @@ func (backRestBackupOpts pgBackRestBackupOptions) validate(setFlagFieldNames []s err := errors.New("Invalid network compress level for pgBackRest backup") errstrings = append(errstrings, err.Error()) } + case "CompressType": + if !isValidCompressType(backRestBackupOpts.CompressType) { + err := errors.New("Invalid compress type for pgBackRest backup") + errstrings = append(errstrings, err.Error()) + } case "LogLevelConsole": if !isValidBackrestLogLevel(backRestBackupOpts.LogLevelConsole) { err := errors.New("Invalid log level for pgBackRest backup") From 7c015ea0e4ebc8b1793aa9e4e278901e78529917 Mon Sep 17 00:00:00 2001 From: Steven Siahetiong Date: Mon, 1 Feb 2021 00:54:39 +0800 Subject: [PATCH 168/276] Replace PG_STAT_STATEMENTS_LIMIT on exporter startup The new variable was added for the `pg_stat_statements` support. This sets the default to be "20" statements, which is what is used by pgMonitor. Co-authored-by: Steven Siahetiong Issue: #2244 --- bin/crunchy-postgres-exporter/start.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/crunchy-postgres-exporter/start.sh b/bin/crunchy-postgres-exporter/start.sh index 42ed3d6d03..d53bbb1658 100755 --- a/bin/crunchy-postgres-exporter/start.sh +++ b/bin/crunchy-postgres-exporter/start.sh @@ -214,7 +214,7 @@ else fi fi -sed -i "s/#PGBACKREST_INFO_THROTTLE_MINUTES#/${PGBACKREST_INFO_THROTTLE_MINUTES:-10}/g" /tmp/queries.yml +sed -i -e "s/#PGBACKREST_INFO_THROTTLE_MINUTES#/${PGBACKREST_INFO_THROTTLE_MINUTES:-10}/g" -e "s/#PG_STAT_STATEMENTS_LIMIT#/${PG_STAT_STATEMENTS_LIMIT:-20}/g" /tmp/queries.yml PG_OPTIONS="--extend.query-path=${QUERY_DIR?}/queries.yml --web.listen-address=:${POSTGRES_EXPORTER_PORT}" From b86fd0e76dd1f2cb9e2b55a4794f62bea38ebbe2 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 31 Jan 2021 12:37:40 -0500 Subject: [PATCH 169/276] Only consider running Pods for scheduled backups Though the scheduled backup code would bail if it found multiple pgBackRest repository Pods, this ensures that only running pgBackRest Pods would be considered. Issue: #2237 --- cmd/pgo-scheduler/scheduler/pgbackrest.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/cmd/pgo-scheduler/scheduler/pgbackrest.go b/cmd/pgo-scheduler/scheduler/pgbackrest.go index 62330d61e1..930d82859d 100644 --- a/cmd/pgo-scheduler/scheduler/pgbackrest.go +++ b/cmd/pgo-scheduler/scheduler/pgbackrest.go @@ -22,8 +22,10 @@ import ( "github.com/crunchydata/postgres-operator/internal/config" log "github.com/sirupsen/logrus" + v1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/util/wait" ) @@ -116,17 +118,20 @@ func (b BackRestBackupJob) Run() { return } - selector := fmt.Sprintf("%s=%s,pgo-backrest-repo=true", config.LABEL_PG_CLUSTER, b.cluster) - pods, err := clientset.CoreV1().Pods(b.namespace).List(ctx, metav1.ListOptions{LabelSelector: selector}) + selector := fmt.Sprintf("%s=%s,%s", config.LABEL_PG_CLUSTER, b.cluster, config.LABEL_PGO_BACKREST_REPO) + options := metav1.ListOptions{ + FieldSelector: fields.OneTermEqualSelector("status.phase", string(v1.PodRunning)).String(), + LabelSelector: selector, + } + + pods, err := clientset.CoreV1().Pods(b.namespace).List(ctx, options) if err != nil { contextLogger.WithFields(log.Fields{ "selector": selector, "error": err, }).Error("error getting pods from selector") return - } - - if len(pods.Items) != 1 { + } else if len(pods.Items) != 1 { contextLogger.WithFields(log.Fields{ "selector": selector, "error": err, From a2187241148daee94a04a00f68e080ed0ae5eee5 Mon Sep 17 00:00:00 2001 From: Andrew L'Ecuyer Date: Fri, 29 Jan 2021 23:43:17 +0000 Subject: [PATCH 170/276] Clean Stanza Create pgtask & Job After Init When a PostgreSQL cluster is initialized for the first time, the stanza create pgtask and the Kubernetes Job associated with it are now automatically deleted. This ensures that subsequent update events in the Job controller for a stanza-create Job do not trigger additional "initial backups" Jobs for the cluster. Issue: [ch10277] Issue: #2106 --- internal/controller/job/backresthandler.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/internal/controller/job/backresthandler.go b/internal/controller/job/backresthandler.go index a55c47f931..bd740e36a7 100644 --- a/internal/controller/job/backresthandler.go +++ b/internal/controller/job/backresthandler.go @@ -170,6 +170,14 @@ func (c *Controller) handleBackrestStanzaCreateUpdate(job *apiv1.Job) error { _, _ = backrest.CreateInitialBackup(c.Client, job.ObjectMeta.Namespace, clusterName, backrestRepoPodName) + // now that the initial backup has been initiated, proceed with deleting the stanza-create + // pgtask and associated Job. This will ensure any subsequent updates to the stanza-create + // Job do not trigger more initial backup Jobs. + if err := backrest.CleanStanzaCreateResources(namespace, clusterName, c.Client); err != nil { + log.Error(err) + return err + } + } return nil } From d3168574822f797cb54c50ff4e6ff3fb75d2a8ba Mon Sep 17 00:00:00 2001 From: Andrew L'Ecuyer Date: Mon, 1 Feb 2021 20:43:03 +0000 Subject: [PATCH 171/276] Handle & Log Initial Backup Creation Errors Errors are now handled and logged when creating an initial cluster backup pgtask following a successful stanza-create Job. --- internal/controller/job/backresthandler.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/internal/controller/job/backresthandler.go b/internal/controller/job/backresthandler.go index bd740e36a7..85330244c1 100644 --- a/internal/controller/job/backresthandler.go +++ b/internal/controller/job/backresthandler.go @@ -167,8 +167,11 @@ func (c *Controller) handleBackrestStanzaCreateUpdate(job *apiv1.Job) error { return err } - _, _ = backrest.CreateInitialBackup(c.Client, job.ObjectMeta.Namespace, - clusterName, backrestRepoPodName) + if _, err := backrest.CreateInitialBackup(c.Client, job.ObjectMeta.Namespace, + clusterName, backrestRepoPodName); err != nil { + log.Error(err) + return err + } // now that the initial backup has been initiated, proceed with deleting the stanza-create // pgtask and associated Job. This will ensure any subsequent updates to the stanza-create From e3ec8fc16a98f253dd790f99221eccb110de5d43 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 1 Feb 2021 16:58:08 -0500 Subject: [PATCH 172/276] Use proper bootstrap template for debug output This also moves the debugging line before the actual template execution to make it possible to more easily debug any issues if the template were customized. --- internal/operator/cluster/clusterlogic.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/internal/operator/cluster/clusterlogic.go b/internal/operator/cluster/clusterlogic.go index 9169d1bedb..bf1a81ea43 100644 --- a/internal/operator/cluster/clusterlogic.go +++ b/internal/operator/cluster/clusterlogic.go @@ -98,15 +98,16 @@ func addClusterBootstrapJob(clientset kubeapi.Interface, return err } + if operator.CRUNCHY_DEBUG { + _ = config.BootstrapTemplate.Execute(os.Stdout, bootstrapFields) + } + var bootstrapSpec bytes.Buffer + if err := config.BootstrapTemplate.Execute(&bootstrapSpec, bootstrapFields); err != nil { return err } - if operator.CRUNCHY_DEBUG { - _ = config.DeploymentTemplate.Execute(os.Stdout, bootstrapFields) - } - job := &batchv1.Job{} if err := json.Unmarshal(bootstrapSpec.Bytes(), job); err != nil { return err From d7309c309e145c12dddd36aeda780fba82041d5a Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 1 Feb 2021 13:41:15 -0500 Subject: [PATCH 173/276] Fix bonus pgBadger sidecar on upgrade We (me) did not put an explicit boolean check around the former pgBadger label, which would cause a bonus pgBadger sidecar to join upgraded clusters. Issue: [ch10298] --- internal/operator/cluster/upgrade.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/operator/cluster/upgrade.go b/internal/operator/cluster/upgrade.go index 93cec5e704..06ea68db8e 100644 --- a/internal/operator/cluster/upgrade.go +++ b/internal/operator/cluster/upgrade.go @@ -504,7 +504,7 @@ func preparePgclusterForUpgrade(pgcluster *crv1.Pgcluster, parameters map[string // 4.6.0 moved pgBadger to use an attribute instead of a label. If this label // exists on the current CRD, move the value to the attribute. - if _, ok := pgcluster.ObjectMeta.GetLabels()["crunchy-pgbadger"]; ok { + if ok, _ := strconv.ParseBool(pgcluster.ObjectMeta.GetLabels()["crunchy-pgbadger"]); ok { pgcluster.Spec.PGBadger = true delete(pgcluster.ObjectMeta.Labels, "crunchy-pgbadger") } From d846f2830cdb499b6d558706ef5923adb08702f7 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 1 Feb 2021 13:50:28 -0500 Subject: [PATCH 174/276] Ensure moot labels are explicitly removed during upgrade Presently, these keys are only removed if there is a value present in them, not if they are empty. This update ensures that they are removed regardless of contents. --- internal/operator/cluster/upgrade.go | 30 +++++++++++++--------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/internal/operator/cluster/upgrade.go b/internal/operator/cluster/upgrade.go index 06ea68db8e..c61cb2cc77 100644 --- a/internal/operator/cluster/upgrade.go +++ b/internal/operator/cluster/upgrade.go @@ -482,46 +482,46 @@ func preparePgclusterForUpgrade(pgcluster *crv1.Pgcluster, parameters map[string // that the value is migrated over if value, ok := pgcluster.ObjectMeta.Labels["crunchy_collect"]; ok { pgcluster.ObjectMeta.Labels[config.LABEL_EXPORTER] = value - delete(pgcluster.ObjectMeta.Labels, "crunchy_collect") } + delete(pgcluster.ObjectMeta.Labels, "crunchy_collect") + // Note that this is the *user labels*, the above is in the metadata labels if value, ok := pgcluster.Spec.UserLabels["crunchy_collect"]; ok { pgcluster.Spec.UserLabels[config.LABEL_EXPORTER] = value - delete(pgcluster.Spec.UserLabels, "crunchy_collect") } + delete(pgcluster.Spec.UserLabels, "crunchy_collect") // convert the metrics label over to using a proper definition. Give the user // label precedence. if value, ok := pgcluster.ObjectMeta.Labels[config.LABEL_EXPORTER]; ok { pgcluster.Spec.Exporter, _ = strconv.ParseBool(value) - delete(pgcluster.ObjectMeta.Labels, config.LABEL_EXPORTER) } + delete(pgcluster.ObjectMeta.Labels, config.LABEL_EXPORTER) + // again, note this is *user* labels, the above are the metadata labels if value, ok := pgcluster.Spec.UserLabels[config.LABEL_EXPORTER]; ok { pgcluster.Spec.Exporter, _ = strconv.ParseBool(value) - delete(pgcluster.Spec.UserLabels, config.LABEL_EXPORTER) } + delete(pgcluster.Spec.UserLabels, config.LABEL_EXPORTER) // 4.6.0 moved pgBadger to use an attribute instead of a label. If this label // exists on the current CRD, move the value to the attribute. if ok, _ := strconv.ParseBool(pgcluster.ObjectMeta.GetLabels()["crunchy-pgbadger"]); ok { pgcluster.Spec.PGBadger = true - delete(pgcluster.ObjectMeta.Labels, "crunchy-pgbadger") } + delete(pgcluster.ObjectMeta.Labels, "crunchy-pgbadger") // 4.6.0 moved the format "service-type" label into the ServiceType CRD // attribute, so we may need to do the same if val, ok := pgcluster.Spec.UserLabels["service-type"]; ok { pgcluster.Spec.ServiceType = v1.ServiceType(val) - delete(pgcluster.Spec.UserLabels, "service-type") } + delete(pgcluster.Spec.UserLabels, "service-type") // 4.6.0 moved the "autofail" label to the DisableAutofail attribute. Given // by default we need to start in an autofailover state, we just delete the // legacy attribute - if _, ok := pgcluster.ObjectMeta.GetLabels()["autofail"]; ok { - delete(pgcluster.ObjectMeta.Labels, "autofail") - } + delete(pgcluster.ObjectMeta.Labels, "autofail") // 4.6.0 moved the node labels to the custom resource objects in a more // structure way. if we have a node label, then let's migrate it to that @@ -548,11 +548,10 @@ func preparePgclusterForUpgrade(pgcluster *crv1.Pgcluster, parameters map[string PreferredDuringSchedulingIgnoredDuringExecution: []v1.PreferredSchedulingTerm{term}, }, } - - // erase all trace of this - delete(pgcluster.Spec.UserLabels, "NodeLabelKey") - delete(pgcluster.Spec.UserLabels, "NodeLabelValue") } + // erase all trace of this + delete(pgcluster.Spec.UserLabels, "NodeLabelKey") + delete(pgcluster.Spec.UserLabels, "NodeLabelValue") // 4.6.0 moved the "backrest-storage-type" label to a CRD attribute, well, // really an array of CRD attributes, which we need to map the various @@ -592,10 +591,9 @@ func preparePgclusterForUpgrade(pgcluster *crv1.Pgcluster, parameters map[string pgcluster.Spec.BackrestStorageTypes = append(pgcluster.Spec.BackrestStorageTypes, crv1.BackrestStorageTypePosix) } - - // and delete the label - delete(pgcluster.Spec.UserLabels, "backrest-storage-type") } + // and delete the label + delete(pgcluster.Spec.UserLabels, "backrest-storage-type") // since the current primary label is not used in this version of the Postgres Operator, // delete it before moving on to other upgrade tasks From 90c98b7f46169a5e1eabf012b0c42111237b7ec3 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 8 Feb 2021 10:03:46 -0500 Subject: [PATCH 175/276] Clarify example in deleting backup command This makes it clear that deleting a backup is on a cluster, not any other artifact. Issue: [ch10372] --- cmd/pgo/cmd/delete.go | 2 +- docs/content/pgo-client/reference/pgo_delete_backup.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/pgo/cmd/delete.go b/cmd/pgo/cmd/delete.go index 8397690492..1d670697af 100644 --- a/cmd/pgo/cmd/delete.go +++ b/cmd/pgo/cmd/delete.go @@ -303,7 +303,7 @@ var deleteBackupCmd = &cobra.Command{ Short: "Delete a backup", Long: `Delete a backup from pgBackRest. Requires a target backup. For example: - pgo delete backup hippo --target=20201220-171801F`, + pgo delete backup clustername --target=20201220-171801F`, Run: func(cmd *cobra.Command, args []string) { if len(args) == 0 { fmt.Println("Error: A cluster name is required for this command.") diff --git a/docs/content/pgo-client/reference/pgo_delete_backup.md b/docs/content/pgo-client/reference/pgo_delete_backup.md index 9b1f091bde..c708d8e12b 100644 --- a/docs/content/pgo-client/reference/pgo_delete_backup.md +++ b/docs/content/pgo-client/reference/pgo_delete_backup.md @@ -9,7 +9,7 @@ Delete a backup Delete a backup from pgBackRest. Requires a target backup. For example: - pgo delete backup hippo --target=20201220-171801F + pgo delete backup clustername --target=20201220-171801F ``` pgo delete backup [flags] From e4f8a10c80f895f622f30b301adadb54aabea6a6 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 8 Feb 2021 10:06:26 -0500 Subject: [PATCH 176/276] Ensure "UserLabels" are explicitly initialized With the changes to the usage of UserLabels, this value was not being properly initialized under certain circumstances (e.g. direct API calls). Issue: [ch10371] Issue: #2262 --- internal/apiserver/clusterservice/clusterimpl.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index 18999d66eb..c0fa4acef5 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -1079,6 +1079,7 @@ func getClusterParams(request *msgs.CreateClusterRequest, name string, ns string Limits: v1.ResourceList{}, Resources: v1.ResourceList{}, }, + UserLabels: map[string]string{}, } // enable the exporter sidecar based on the what the user passed in or what @@ -1358,7 +1359,9 @@ func getClusterParams(request *msgs.CreateClusterRequest, name string, ns string spec.ServiceType = request.ServiceType - spec.UserLabels = request.UserLabels + if request.UserLabels != nil { + spec.UserLabels = request.UserLabels + } spec.UserLabels[config.LABEL_PGO_VERSION] = msgs.PGO_VERSION // override any values from config file From 7fe24914bc998515fe5a444e3677ab6fc95e646f Mon Sep 17 00:00:00 2001 From: tjmoore4 <42497036+tjmoore4@users.noreply.github.com> Date: Mon, 8 Feb 2021 13:37:17 -0500 Subject: [PATCH 177/276] Unix socket directory update during upgrade This update set the unix_socket_directories parameter in the PG configuration to /tmp. This is done to ensure any existing references to the /crunchyadm directory are no longer set in the PG configuration when a cluster is upgraded. As this value is stored in the DCS according to the key/value pairs defined in the PG configmap, it is corrected by updating the -pgha-config ConfigMap to no longer reference the removed directory. Similarly, any existing references to scripts in the previous /opt/cpm paths stored in existing configmaps will be updated and synchronized with the DCS to the current /opt/crunchy paths currently defined in the associated containers. Issue: [ch10283] Issue: #2239 --- internal/operator/cluster/upgrade.go | 83 ++++++++++++++++++++++++++ internal/operator/config/configutil.go | 4 +- internal/operator/config/dcs.go | 4 +- internal/operator/config/localdb.go | 10 ++-- 4 files changed, 92 insertions(+), 9 deletions(-) diff --git a/internal/operator/cluster/upgrade.go b/internal/operator/cluster/upgrade.go index c61cb2cc77..22fd84426e 100644 --- a/internal/operator/cluster/upgrade.go +++ b/internal/operator/cluster/upgrade.go @@ -27,6 +27,7 @@ import ( "github.com/crunchydata/postgres-operator/internal/config" "github.com/crunchydata/postgres-operator/internal/kubeapi" "github.com/crunchydata/postgres-operator/internal/operator" + pgoconfig "github.com/crunchydata/postgres-operator/internal/operator/config" "github.com/crunchydata/postgres-operator/internal/util" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" "github.com/crunchydata/postgres-operator/pkg/events" @@ -110,6 +111,12 @@ func AddUpgrade(clientset kubeapi.Interface, upgrade *crv1.Pgtask, namespace str return } + // update the unix socket directories parameter so it no longer include /crunchyadm and + // set any path references to the /opt/crunchy... paths + if err = updateClusterConfig(clientset, pgcluster, namespace); err != nil { + log.Errorf("error updating %s-pgha-config configmap during upgrade of cluster %s, Error: %v", pgcluster.Name, pgcluster.Name, err) + } + // recreate new Backrest Repo secret that was just deleted recreateBackrestRepoSecret(clientset, upgradeTargetClusterName, namespace, operator.PgoNamespace) @@ -282,6 +289,7 @@ func deleteBeforeUpgrade(clientset kubeapi.Interface, pgcluster *crv1.Pgcluster, List(ctx, metav1.ListOptions{LabelSelector: config.LABEL_PG_CLUSTER + "=" + pgcluster.Name}) if err != nil { log.Errorf("unable to get deployments. Error: %s", err) + return err } // next, delete those deployments @@ -838,3 +846,78 @@ func publishUpgradeClusterFailureEvent(eventHeader events.EventHeader, clusterna log.Errorf("error publishing event. Error: %s", err.Error()) } } + +// updateClusterConfig updates PG configuration for cluster via its Distributed Configuration Store +// (DCS) according to the key/value pairs defined in the pgConfig map, specifically by updating +// the -pgha-config ConfigMap. The configuration settings specified are +// applied to the entire cluster via the DCS configuration included within this the +// -pgha-config ConfigMap. +func updateClusterConfig(clientset kubeapi.Interface, pgcluster *crv1.Pgcluster, namespace string) error { + + // first, define the names for the two main sections of the -pgha-config configmap + + // -dcs-config + dcsConfigName := fmt.Sprintf(pgoconfig.PGHADCSConfigName, pgcluster.Name) + // -local-config + localConfigName := fmt.Sprintf(pgoconfig.PGHALocalConfigName, pgcluster.Name) + + // next, get the -pgha-config configmap + clusterConfig, err := clientset.CoreV1().ConfigMaps(namespace).Get(context.TODO(), fmt.Sprintf("%s-pgha-config", pgcluster.Name), metav1.GetOptions{}) + if err != nil { + return err + } + + // prepare DCS config struct + dcsConf := &pgoconfig.DCSConfig{} + if err := yaml.Unmarshal([]byte(clusterConfig.Data[dcsConfigName]), dcsConf); err != nil { + return err + } + + // prepare LocalDB config struct + localDBConf := &pgoconfig.LocalDBConfig{} + if err := yaml.Unmarshal([]byte(clusterConfig.Data[localConfigName]), localDBConf); err != nil { + return err + } + + // set the updated path values for both DCS and LocalDB configs + // as of version 4.6, the /crunchyadm directory no longer exists (previously set as a unix socket directory) + // and the /opt/cpm... directories are now set under /opt/crunchy + dcsConf.PostgreSQL.Parameters["unix_socket_directories"] = "/tmp" + dcsConf.PostgreSQL.Parameters["archive_command"] = `source /opt/crunchy/bin/postgres-ha/pgbackrest/pgbackrest-set-env.sh && pgbackrest archive-push "%p"` + dcsConf.PostgreSQL.RecoveryConf["restore_command"] = `source /opt/crunchy/bin/postgres-ha/pgbackrest/pgbackrest-set-env.sh && pgbackrest archive-get %f "%p"` + + localDBConf.PostgreSQL.Callbacks.OnRoleChange = "/opt/crunchy/bin/postgres-ha/callbacks/pgha-on-role-change.sh" + localDBConf.PostgreSQL.PGBackRest.Command = "/opt/crunchy/bin/postgres-ha/pgbackrest/pgbackrest-create-replica.sh replica" + localDBConf.PostgreSQL.PGBackRestStandby.Command = "/opt/crunchy/bin/postgres-ha/pgbackrest/pgbackrest-create-replica.sh standby" + + // set up content and patch DCS config + dcsContent, err := yaml.Marshal(dcsConf) + if err != nil { + return err + } + + // patch the configmap with the DCS config updates + if err := pgoconfig.PatchConfigMapData(clientset, clusterConfig, dcsConfigName, dcsContent); err != nil { + return err + } + + // set up content and patch localDB config + localDBContent, err := yaml.Marshal(localDBConf) + if err != nil { + return err + } + + // patch the configmap with the localDB config updates + if err := pgoconfig.PatchConfigMapData(clientset, clusterConfig, localConfigName, localDBContent); err != nil { + return err + } + + // get the newly patched -pgha-config configmap + patchedClusterConfig, err := clientset.CoreV1().ConfigMaps(namespace).Get(context.TODO(), fmt.Sprintf("%s-pgha-config", pgcluster.Name), metav1.GetOptions{}) + if err != nil { + return err + } + + // sync the changes to the configmap to the DCS + return pgoconfig.NewDCS(patchedClusterConfig, clientset, pgcluster.GetObjectMeta().GetLabels()[config.LABEL_PGHA_SCOPE]).Sync() +} diff --git a/internal/operator/config/configutil.go b/internal/operator/config/configutil.go index 3205d35284..5d8648b098 100644 --- a/internal/operator/config/configutil.go +++ b/internal/operator/config/configutil.go @@ -51,9 +51,9 @@ type Syncer interface { Sync() error } -// patchConfigMapData replaces the configuration stored the configuration specified with the +// PatchConfigMapData replaces the configuration stored the configuration specified with the // provided content -func patchConfigMapData(kubeclientset kubernetes.Interface, configMap *corev1.ConfigMap, +func PatchConfigMapData(kubeclientset kubernetes.Interface, configMap *corev1.ConfigMap, configName string, content []byte) error { ctx := context.TODO() diff --git a/internal/operator/config/dcs.go b/internal/operator/config/dcs.go index 16238bfdf4..270330c4cd 100644 --- a/internal/operator/config/dcs.go +++ b/internal/operator/config/dcs.go @@ -148,7 +148,7 @@ func (d *DCS) Update(dcsConfig *DCSConfig) error { return err } - if err := patchConfigMapData(d.kubeclientset, d.configMap, d.configName, content); err != nil { + if err := PatchConfigMapData(d.kubeclientset, d.configMap, d.configName, content); err != nil { return err } @@ -301,7 +301,7 @@ func (d *DCS) refresh() error { return err } - if err := patchConfigMapData(d.kubeclientset, d.configMap, d.configName, + if err := PatchConfigMapData(d.kubeclientset, d.configMap, d.configName, clusterDCSBytes); err != nil { return err } diff --git a/internal/operator/config/localdb.go b/internal/operator/config/localdb.go index 76d641d38e..eab42dd6bf 100644 --- a/internal/operator/config/localdb.go +++ b/internal/operator/config/localdb.go @@ -48,9 +48,9 @@ var ( // when the script is called. applyAndReloadConfigCMD []string = []string{"/opt/crunchy/bin/postgres-ha/common/pgha-reload-local.sh"} - // pghaLocalConfigName represents the name of the local configuration stored for each database + // PGHALocalConfigName represents the name of the local configuration stored for each database // server in the "-pgha-config" configMap, which is "-local-config" - pghaLocalConfigName = "%s-local-config" + PGHALocalConfigName = "%s-local-config" // pghaLocalConfigSuffix is the suffix for a local server configuration pghaLocalConfigSuffix = "-local-config" ) @@ -203,7 +203,7 @@ func (l *LocalDB) Update(configName string, localDBConfig LocalDBConfig) error { return err } - if err := patchConfigMapData(l.kubeclientset, l.configMap, configName, content); err != nil { + if err := PatchConfigMapData(l.kubeclientset, l.configMap, configName, content); err != nil { return err } @@ -388,7 +388,7 @@ func (l *LocalDB) refresh(configName string) error { return err } - if err := patchConfigMapData(l.kubeclientset, l.configMap, configName, + if err := PatchConfigMapData(l.kubeclientset, l.configMap, configName, localConfigYAML); err != nil { return err } @@ -418,7 +418,7 @@ func GetLocalDBConfigNames(kubeclientset kubernetes.Interface, clusterName, localConfigNames := make([]string, len(dbDeploymentList.Items)) for i, deployment := range dbDeploymentList.Items { - localConfigNames[i] = fmt.Sprintf(pghaLocalConfigName, deployment.GetName()) + localConfigNames[i] = fmt.Sprintf(PGHALocalConfigName, deployment.GetName()) } return localConfigNames, nil From b5be866450f6e9c973d44402ea1c11906e28a61b Mon Sep 17 00:00:00 2001 From: Joseph Mckulka <16840147+jmckulk@users.noreply.github.com> Date: Tue, 9 Feb 2021 14:00:52 -0500 Subject: [PATCH 178/276] Update restore e2e test Restores now keep the original pvc and perform a delta restore instead of re-creating the pvc and running a full restore. This test was checking to confirm that the pvc was re-created as part of the restore process. Since the original pvc is not re-created, we should now check that the creation time for the cluster pvc pre-restore and post-restore is the same. --- testing/pgo_cli/cluster_backup_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testing/pgo_cli/cluster_backup_test.go b/testing/pgo_cli/cluster_backup_test.go index ceeefe2e5a..527e8762ec 100644 --- a/testing/pgo_cli/cluster_backup_test.go +++ b/testing/pgo_cli/cluster_backup_test.go @@ -168,7 +168,7 @@ func TestClusterBackup(t *testing.T) { }) t.Run("restore", func(t *testing.T) { - t.Run("replaces the cluster", func(t *testing.T) { + t.Run("keeps existing pvc", func(t *testing.T) { t.Parallel() withCluster(t, namespace, func(cluster func() string) { requireClusterReady(t, namespace(), cluster(), time.Minute) @@ -195,10 +195,10 @@ func TestClusterBackup(t *testing.T) { after := clusterPVCs(t, namespace(), cluster()) for _, pvc := range after { // check to see if the PVC for the primary is bound, and has a timestamp - // after the original timestamp for the primary PVC timestamp captured above, - // indicating that it been re-created + // equal to the original timestamp for the primary PVC timestamp captured above, + // indicating that it has not been re-created if pvc.GetName() == cluster() && kubeapi.IsPVCBound(pvc) && - pvc.GetCreationTimestamp().Time.After(primaryPVCCreationTimestamp) { + pvc.GetCreationTimestamp().Time.Equal(primaryPVCCreationTimestamp) { return true } } From c25feebdb2527a812a50bfd38b326766724c3f58 Mon Sep 17 00:00:00 2001 From: TJ Moore Date: Mon, 8 Feb 2021 17:41:33 -0500 Subject: [PATCH 179/276] Check nested struct initialization during CM upgrade During the upgrade of an existing pgcluster, the existing -pgha-config configmap is used to populate the struct object so that key values are upgraded, as needed. In certain cases where the configmap exists but was is not initialized as expected, the exist function can hit a runtime panic when referencing uninitialized structs or maps. This update checks that these structs and maps are not nil before attempting to set any values. --- internal/operator/cluster/upgrade.go | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/internal/operator/cluster/upgrade.go b/internal/operator/cluster/upgrade.go index 22fd84426e..ec979db6da 100644 --- a/internal/operator/cluster/upgrade.go +++ b/internal/operator/cluster/upgrade.go @@ -879,16 +879,24 @@ func updateClusterConfig(clientset kubeapi.Interface, pgcluster *crv1.Pgcluster, return err } - // set the updated path values for both DCS and LocalDB configs + // set the updated path values for both DCS and LocalDB configs, if the fields and maps exist // as of version 4.6, the /crunchyadm directory no longer exists (previously set as a unix socket directory) // and the /opt/cpm... directories are now set under /opt/crunchy - dcsConf.PostgreSQL.Parameters["unix_socket_directories"] = "/tmp" - dcsConf.PostgreSQL.Parameters["archive_command"] = `source /opt/crunchy/bin/postgres-ha/pgbackrest/pgbackrest-set-env.sh && pgbackrest archive-push "%p"` - dcsConf.PostgreSQL.RecoveryConf["restore_command"] = `source /opt/crunchy/bin/postgres-ha/pgbackrest/pgbackrest-set-env.sh && pgbackrest archive-get %f "%p"` + if dcsConf.PostgreSQL != nil && dcsConf.PostgreSQL.Parameters != nil { + dcsConf.PostgreSQL.Parameters["unix_socket_directories"] = "/tmp" + dcsConf.PostgreSQL.Parameters["archive_command"] = `source /opt/crunchy/bin/postgres-ha/pgbackrest/pgbackrest-set-env.sh && pgbackrest archive-push "%p"` + dcsConf.PostgreSQL.RecoveryConf["restore_command"] = `source /opt/crunchy/bin/postgres-ha/pgbackrest/pgbackrest-set-env.sh && pgbackrest archive-get %f "%p"` + } - localDBConf.PostgreSQL.Callbacks.OnRoleChange = "/opt/crunchy/bin/postgres-ha/callbacks/pgha-on-role-change.sh" - localDBConf.PostgreSQL.PGBackRest.Command = "/opt/crunchy/bin/postgres-ha/pgbackrest/pgbackrest-create-replica.sh replica" - localDBConf.PostgreSQL.PGBackRestStandby.Command = "/opt/crunchy/bin/postgres-ha/pgbackrest/pgbackrest-create-replica.sh standby" + if localDBConf.PostgreSQL.Callbacks != nil { + localDBConf.PostgreSQL.Callbacks.OnRoleChange = "/opt/crunchy/bin/postgres-ha/callbacks/pgha-on-role-change.sh" + } + if localDBConf.PostgreSQL.PGBackRest != nil { + localDBConf.PostgreSQL.PGBackRest.Command = "/opt/crunchy/bin/postgres-ha/pgbackrest/pgbackrest-create-replica.sh replica" + } + if localDBConf.PostgreSQL.PGBackRestStandby != nil { + localDBConf.PostgreSQL.PGBackRestStandby.Command = "/opt/crunchy/bin/postgres-ha/pgbackrest/pgbackrest-create-replica.sh standby" + } // set up content and patch DCS config dcsContent, err := yaml.Marshal(dcsConf) From c0db2eb799c97ad383d282b312cb617bc3088455 Mon Sep 17 00:00:00 2001 From: TJ Moore Date: Mon, 8 Feb 2021 17:47:33 -0500 Subject: [PATCH 180/276] Ensure proper image tag values are authoritative During both pgcluster create and upgrade, the --ccp-image-tag flag can be used to specify an image tag that overrides the value set in the PostgreSQL Operator default configuration. In certain cases, this value is not authoritative because the underlying ccpimagetag CRD value is not honored as expected. This update standardizes the behavior throughout the Operator so that the CLI flag and subsequent CRD value is authoritative both during initial cluster creation/upgrade and also used during subsequent opertions, such as backups and restores. This update is designed to conform to the standard ccpimagetag pattern. For example, the tag centos8-12.5-4.6.0 will correspond to the image OS version, PostgreSQL version and Crunchy PostgreSQL Operator version. Similarly, a PostGIS enabled image would be expected to have a tag similar to centos8-12.5-3.0-4.6.0 where the additional value, 3.0, corresponds to the PostGIS version. This is particularly important to note when using PostGIS enabled clusters, as the Operator will automatically handle the removal of the '3.0' value when creating sidecars or companion pods, such as pgBadger or pgBouncer. Finally, the tag validation used during the Operator's automated upgrade process has been adjusted to properly perform tag validation based on the provide image tag when the --ccp-image-tag flag is used. --- .../apiserver/upgradeservice/upgradeimpl.go | 6 +++--- internal/operator/backrest/backup.go | 19 ++++++++++--------- internal/operator/backrest/repo.go | 5 +++-- internal/operator/cluster/pgadmin.go | 5 +++-- internal/operator/cluster/pgbouncer.go | 9 +++++---- internal/operator/pgdump/dump.go | 19 ++++++++++--------- internal/operator/pgdump/restore.go | 7 ++++--- 7 files changed, 38 insertions(+), 32 deletions(-) diff --git a/internal/apiserver/upgradeservice/upgradeimpl.go b/internal/apiserver/upgradeservice/upgradeimpl.go index 861cbbe655..05d67e932f 100644 --- a/internal/apiserver/upgradeservice/upgradeimpl.go +++ b/internal/apiserver/upgradeservice/upgradeimpl.go @@ -166,10 +166,10 @@ func CreateUpgrade(request *msgs.CreateUpgradeRequest, ns, pgouser string) msgs. // image tag (first value) is compatible (i.e. is the same Major PostgreSQL version) as the // existing cluster's PG value, unless the --ignore-validation flag is set or the --post-gis-image-tag // flag is used - if !upgradeTagValid(cl.Spec.CCPImageTag, apiserver.Pgo.Cluster.CCPImageTag) && !request.IgnoreValidation && request.UpgradeCCPImageTag != "" { - log.Debugf("Cannot upgrade from %s to %s. Image must be the same base OS and the upgrade must be within the same major PG version.", cl.Spec.CCPImageTag, apiserver.Pgo.Cluster.CCPImageTag) + if !upgradeTagValid(cl.Spec.CCPImageTag, spec.Parameters[config.LABEL_CCP_IMAGE_KEY]) && !request.IgnoreValidation && spec.Parameters[config.LABEL_CCP_IMAGE_KEY] != "" { + log.Debugf("Cannot upgrade from %s to %s. Image must be the same base OS and the upgrade must be within the same major PG version.", cl.Spec.CCPImageTag, spec.Parameters[config.LABEL_CCP_IMAGE_KEY]) response.Status.Code = msgs.Error - response.Status.Msg = fmt.Sprintf("cannot upgrade from %s to %s, upgrade task failed.", cl.Spec.CCPImageTag, apiserver.Pgo.Cluster.CCPImageTag) + response.Status.Msg = fmt.Sprintf("cannot upgrade from %s to %s, upgrade task failed.", cl.Spec.CCPImageTag, spec.Parameters[config.LABEL_CCP_IMAGE_KEY]) return response } diff --git a/internal/operator/backrest/backup.go b/internal/operator/backrest/backup.go index 87cacf49f9..1eada2d930 100644 --- a/internal/operator/backrest/backup.go +++ b/internal/operator/backrest/backup.go @@ -101,15 +101,16 @@ func Backrest(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask) // create the Job to run the backrest command jobFields := backrestJobTemplateFields{ - JobName: task.Spec.Parameters[config.LABEL_JOB_NAME], - ClusterName: task.Spec.Parameters[config.LABEL_PG_CLUSTER], - PodName: task.Spec.Parameters[config.LABEL_POD_NAME], - SecurityContext: "{}", - Command: cmd, - CommandOpts: task.Spec.Parameters[config.LABEL_BACKREST_OPTS], - PITRTarget: "", - CCPImagePrefix: util.GetValueOrDefault(task.Spec.Parameters[config.LABEL_IMAGE_PREFIX], operator.Pgo.Cluster.CCPImagePrefix), - CCPImageTag: operator.Pgo.Cluster.CCPImageTag, + JobName: task.Spec.Parameters[config.LABEL_JOB_NAME], + ClusterName: task.Spec.Parameters[config.LABEL_PG_CLUSTER], + PodName: task.Spec.Parameters[config.LABEL_POD_NAME], + SecurityContext: "{}", + Command: cmd, + CommandOpts: task.Spec.Parameters[config.LABEL_BACKREST_OPTS], + PITRTarget: "", + CCPImagePrefix: util.GetValueOrDefault(task.Spec.Parameters[config.LABEL_IMAGE_PREFIX], operator.Pgo.Cluster.CCPImagePrefix), + CCPImageTag: util.GetValueOrDefault(util.GetStandardImageTag(cluster.Spec.CCPImage, cluster.Spec.CCPImageTag), + operator.Pgo.Cluster.CCPImageTag), PgbackrestStanza: task.Spec.Parameters[config.LABEL_PGBACKREST_STANZA], PgbackrestDBPath: task.Spec.Parameters[config.LABEL_PGBACKREST_DB_PATH], PgbackrestRepo1Path: task.Spec.Parameters[config.LABEL_PGBACKREST_REPO_PATH], diff --git a/internal/operator/backrest/repo.go b/internal/operator/backrest/repo.go index f06019a633..f1b06b4756 100644 --- a/internal/operator/backrest/repo.go +++ b/internal/operator/backrest/repo.go @@ -228,8 +228,9 @@ func getRepoDeploymentFields(clientset kubernetes.Interface, cluster *crv1.Pgclu replicas int) *RepoDeploymentTemplateFields { repoFields := RepoDeploymentTemplateFields{ - CCPImagePrefix: util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, operator.Pgo.Cluster.CCPImagePrefix), - CCPImageTag: operator.Pgo.Cluster.CCPImageTag, + CCPImagePrefix: util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, operator.Pgo.Cluster.CCPImagePrefix), + CCPImageTag: util.GetValueOrDefault(util.GetStandardImageTag(cluster.Spec.CCPImage, cluster.Spec.CCPImageTag), + operator.Pgo.Cluster.CCPImageTag), ContainerResources: operator.GetResourcesJSON(cluster.Spec.BackrestResources, cluster.Spec.BackrestLimits), BackrestRepoClaimName: fmt.Sprintf(util.BackrestRepoPVCName, cluster.Name), SshdSecretsName: fmt.Sprintf(util.BackrestRepoSecretName, cluster.Name), diff --git a/internal/operator/cluster/pgadmin.go b/internal/operator/cluster/pgadmin.go index 9f058a89b7..ee9ec1ddb9 100644 --- a/internal/operator/cluster/pgadmin.go +++ b/internal/operator/cluster/pgadmin.go @@ -354,8 +354,9 @@ func createPgAdminDeployment(clientset kubernetes.Interface, cluster *crv1.Pgclu fields := pgAdminTemplateFields{ Name: pgAdminDeploymentName, ClusterName: cluster.Name, - CCPImagePrefix: operator.Pgo.Cluster.CCPImagePrefix, - CCPImageTag: util.GetStandardImageTag(cluster.Spec.CCPImage, cluster.Spec.CCPImageTag), + CCPImagePrefix: util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, operator.Pgo.Cluster.CCPImagePrefix), + CCPImageTag: util.GetValueOrDefault(util.GetStandardImageTag(cluster.Spec.CCPImage, cluster.Spec.CCPImageTag), + operator.Pgo.Cluster.CCPImageTag), DisableFSGroup: operator.Pgo.Cluster.DisableFSGroup, Port: defPgAdminPort, InitUser: defSetupUsername, diff --git a/internal/operator/cluster/pgbouncer.go b/internal/operator/cluster/pgbouncer.go index 1cb6b72716..91dc99de2b 100644 --- a/internal/operator/cluster/pgbouncer.go +++ b/internal/operator/cluster/pgbouncer.go @@ -562,10 +562,11 @@ func createPgBouncerDeployment(clientset kubernetes.Interface, cluster *crv1.Pgc // get the fields that will be substituted in the pgBouncer template fields := pgBouncerTemplateFields{ - Name: pgbouncerDeploymentName, - ClusterName: cluster.Name, - CCPImagePrefix: util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, operator.Pgo.Cluster.CCPImagePrefix), - CCPImageTag: util.GetStandardImageTag(cluster.Spec.CCPImage, cluster.Spec.CCPImageTag), + Name: pgbouncerDeploymentName, + ClusterName: cluster.Name, + CCPImagePrefix: util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, operator.Pgo.Cluster.CCPImagePrefix), + CCPImageTag: util.GetValueOrDefault(util.GetStandardImageTag(cluster.Spec.CCPImage, cluster.Spec.CCPImageTag), + operator.Pgo.Cluster.CCPImageTag), DisableFSGroup: operator.Pgo.Cluster.DisableFSGroup, Port: cluster.Spec.Port, PGBouncerConfigMap: util.GeneratePgBouncerConfigMapName(cluster.Name), diff --git a/internal/operator/pgdump/dump.go b/internal/operator/pgdump/dump.go index 0808bedf4f..3a19a32ac3 100644 --- a/internal/operator/pgdump/dump.go +++ b/internal/operator/pgdump/dump.go @@ -103,15 +103,16 @@ func Dump(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask) { jobName := taskName + "-" + util.RandStringBytesRmndr(4) jobFields := pgDumpJobTemplateFields{ - JobName: jobName, - TaskName: taskName, - ClusterName: task.Spec.Parameters[config.LABEL_PG_CLUSTER], - PodName: task.Spec.Parameters[config.LABEL_POD_NAME], - SecurityContext: operator.GetPodSecurityContext(task.Spec.StorageSpec.GetSupplementalGroups()), - Command: cmd, //?? - CommandOpts: task.Spec.Parameters[config.LABEL_PGDUMP_OPTS], - CCPImagePrefix: util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, operator.Pgo.Cluster.CCPImagePrefix), - CCPImageTag: operator.Pgo.Cluster.CCPImageTag, + JobName: jobName, + TaskName: taskName, + ClusterName: task.Spec.Parameters[config.LABEL_PG_CLUSTER], + PodName: task.Spec.Parameters[config.LABEL_POD_NAME], + SecurityContext: operator.GetPodSecurityContext(task.Spec.StorageSpec.GetSupplementalGroups()), + Command: cmd, //?? + CommandOpts: task.Spec.Parameters[config.LABEL_PGDUMP_OPTS], + CCPImagePrefix: util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, operator.Pgo.Cluster.CCPImagePrefix), + CCPImageTag: util.GetValueOrDefault(util.GetStandardImageTag(cluster.Spec.CCPImage, cluster.Spec.CCPImageTag), + operator.Pgo.Cluster.CCPImageTag), PgDumpHost: task.Spec.Parameters[config.LABEL_PGDUMP_HOST], PgDumpUserSecret: task.Spec.Parameters[config.LABEL_PGDUMP_USER], PgDumpDB: task.Spec.Parameters[config.LABEL_PGDUMP_DB], diff --git a/internal/operator/pgdump/restore.go b/internal/operator/pgdump/restore.go index 10e6567cb4..0603f179d7 100644 --- a/internal/operator/pgdump/restore.go +++ b/internal/operator/pgdump/restore.go @@ -105,9 +105,10 @@ func Restore(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask) { PGRestoreOpts: task.Spec.Parameters[config.LABEL_PGRESTORE_OPTS], PITRTarget: task.Spec.Parameters[config.LABEL_PGRESTORE_PITR_TARGET], CCPImagePrefix: util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, operator.Pgo.Cluster.CCPImagePrefix), - CCPImageTag: operator.Pgo.Cluster.CCPImageTag, - NodeSelector: operator.GetNodeAffinity(nodeAffinity), - Tolerations: util.GetTolerations(cluster.Spec.Tolerations), + CCPImageTag: util.GetValueOrDefault(util.GetStandardImageTag(cluster.Spec.CCPImage, cluster.Spec.CCPImageTag), + operator.Pgo.Cluster.CCPImageTag), + NodeSelector: operator.GetNodeAffinity(nodeAffinity), + Tolerations: util.GetTolerations(cluster.Spec.Tolerations), } var doc2 bytes.Buffer From f6d2eaeeff36fe737f3a76487f96dd5cc0595bb0 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Thu, 11 Feb 2021 18:36:01 -0500 Subject: [PATCH 181/276] Existence check for primary Deployment before scaling During the cluster initialization process, there is a step where the Deployment object representing the primary is explicitly scaled from 0 to 1 Pods. However, there is a case that may trigger the Deployment scaling before the Deployment is created. As such, we can add a guard to check that the Deployment is actually created before proceeding with the scaling operation. This patch also modifies a debug line around the cluster Deployment creation, as the debug output came after the point at which the code may have exited due to error. --- internal/controller/pod/inithandler.go | 48 ++++++++++++++++++++++- internal/operator/cluster/clusterlogic.go | 8 ++-- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/internal/controller/pod/inithandler.go b/internal/controller/pod/inithandler.go index d5b3b10bca..444159f231 100644 --- a/internal/controller/pod/inithandler.go +++ b/internal/controller/pod/inithandler.go @@ -18,6 +18,7 @@ limitations under the License. import ( "context" "fmt" + "time" "github.com/crunchydata/postgres-operator/internal/config" "github.com/crunchydata/postgres-operator/internal/controller" @@ -28,12 +29,14 @@ import ( taskoperator "github.com/crunchydata/postgres-operator/internal/operator/task" "github.com/crunchydata/postgres-operator/internal/util" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" + + log "github.com/sirupsen/logrus" apiv1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/types" - - log "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/util/wait" ) // handleClusterInit is responsible for proceeding with initialization of the PG cluster once the @@ -83,6 +86,16 @@ func (c *Controller) handleBackRestRepoInit(newPod *apiv1.Pod, cluster *crv1.Pgc return nil } + // first: a sanity check that there exists a primary deployment to scale. this + // is to attempt to avoid any silent failures in the deployment scaling + // function. + // + // If we do encounter an error, we will proceed in case the deployment becomes + // available after. + if err := c.waitForPrimaryDeployment(cluster); err != nil { + log.Warn(err) + } + clusterInfo, err := clusteroperator.ScaleClusterDeployments(c.Client, *cluster, 1, true, false, false, false) if err != nil { @@ -291,3 +304,34 @@ func (c *Controller) labelPostgresPodAndDeployment(newpod *apiv1.Pod) { return } } + +// waitForPrimaryDeployment checks to see that a primary deployment is +// available. It does not check readiness, only that the deployment exists. This +// used before scaling to ensure scaling does not fail silently +func (c *Controller) waitForPrimaryDeployment(cluster *crv1.Pgcluster) error { + ctx := context.TODO() + primaryDeploymentName := cluster.Annotations[config.ANNOTATION_CURRENT_PRIMARY] + options := metav1.ListOptions{ + LabelSelector: fields.AndSelectors( + fields.OneTermEqualSelector(config.LABEL_PG_CLUSTER, cluster.Name), + fields.OneTermEqualSelector(config.LABEL_PG_DATABASE, config.LABEL_TRUE), + fields.OneTermEqualSelector(config.LABEL_DEPLOYMENT_NAME, primaryDeploymentName), + ).String(), + } + + // start polling to see if the primary deployment is created + if err := wait.PollImmediate(5*time.Second, 60*time.Second, func() (bool, error) { + // check to see if the deployment exists + d, err := c.Client.AppsV1().Deployments(cluster.Namespace).List(ctx, options) + + if err != nil { + log.Warnf("could not find primary deployment for scaling: %s", err) + } + + return err == nil && len(d.Items) > 0, nil + }); err != nil { + return fmt.Errorf("primary deployment lookup timeout reached for %q", primaryDeploymentName) + } + + return nil +} diff --git a/internal/operator/cluster/clusterlogic.go b/internal/operator/cluster/clusterlogic.go index bf1a81ea43..c839527684 100644 --- a/internal/operator/cluster/clusterlogic.go +++ b/internal/operator/cluster/clusterlogic.go @@ -143,15 +143,15 @@ func addClusterDeployments(clientset kubeapi.Interface, deploymentFields := getClusterDeploymentFields(clientset, cl, dataVolume, tablespaceVolumes) + if operator.CRUNCHY_DEBUG { + _ = config.DeploymentTemplate.Execute(os.Stdout, deploymentFields) + } + var primaryDoc bytes.Buffer if err := config.DeploymentTemplate.Execute(&primaryDoc, deploymentFields); err != nil { return err } - if operator.CRUNCHY_DEBUG { - _ = config.DeploymentTemplate.Execute(os.Stdout, deploymentFields) - } - deployment := &appsv1.Deployment{} if err := json.Unmarshal(primaryDoc.Bytes(), deployment); err != nil { return err From 436012109ab5b92fb5f1e4114b1372e85e7022b0 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Thu, 11 Feb 2021 18:42:46 -0500 Subject: [PATCH 182/276] Modify primary check before taking backup This was using a legacy method and is now modified to use the method used by other Operator operations to check for both the primary cluster and its availability. Issue: #2272 --- .../apiserver/backrestservice/backrestimpl.go | 51 ++++++------------- 1 file changed, 15 insertions(+), 36 deletions(-) diff --git a/internal/apiserver/backrestservice/backrestimpl.go b/internal/apiserver/backrestservice/backrestimpl.go index b4b14830f9..67c276fe14 100644 --- a/internal/apiserver/backrestservice/backrestimpl.go +++ b/internal/apiserver/backrestservice/backrestimpl.go @@ -205,10 +205,9 @@ func CreateBackup(request *msgs.CreateBackrestBackupRequest, ns, pgouser string) } // check if primary is ready - if err := isPrimaryReady(cluster, ns); err != nil { - log.Error(err) + if !isPrimaryReady(cluster) { resp.Status.Code = msgs.Error - resp.Status.Msg = err.Error() + resp.Status.Msg = "primary pod is not in Ready state" return resp } @@ -342,49 +341,29 @@ func getBackrestRepoPodName(cluster *crv1.Pgcluster) (string, error) { return repopodName, err } -func isPrimary(pod *v1.Pod, clusterName string) bool { - return pod.ObjectMeta.Labels[config.LABEL_SERVICE_NAME] == clusterName -} - -func isReady(pod *v1.Pod) bool { - readyCount := 0 - containerCount := 0 - for _, stat := range pod.Status.ContainerStatuses { - containerCount++ - if stat.Ready { - readyCount++ - } - } - - return readyCount == containerCount -} - // isPrimaryReady goes through the pod list to first identify the // Primary pod and, once identified, determine if it is in a // ready state. If not, it returns an error, otherwise it returns // a nil value -func isPrimaryReady(cluster *crv1.Pgcluster, ns string) error { +func isPrimaryReady(cluster *crv1.Pgcluster) bool { ctx := context.TODO() - primaryReady := false - selector := fmt.Sprintf("%s=%s,%s=%s", config.LABEL_PG_CLUSTER, cluster.Name, - config.LABEL_PGHA_ROLE, config.LABEL_PGHA_ROLE_PRIMARY) + options := metav1.ListOptions{ + FieldSelector: fields.OneTermEqualSelector("status.phase", string(v1.PodRunning)).String(), + LabelSelector: fields.AndSelectors( + fields.OneTermEqualSelector(config.LABEL_PG_CLUSTER, cluster.Name), + fields.OneTermEqualSelector(config.LABEL_PGHA_ROLE, config.LABEL_PGHA_ROLE_PRIMARY), + ).String(), + } + + pods, err := apiserver.Clientset.CoreV1().Pods(cluster.Namespace).List(ctx, options) - pods, err := apiserver.Clientset.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{LabelSelector: selector}) if err != nil { - return err - } - for i := range pods.Items { - p := &pods.Items[i] - if isPrimary(p, cluster.Spec.Name) && isReady(p) { - primaryReady = true - } + log.Error(err) + return false } - if !primaryReady { - return errors.New("primary pod is not in Ready state") - } - return nil + return len(pods.Items) > 0 } // ShowBackrest ... From 506232cd6456cad7e071f4758e81051c1bf640b5 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Fri, 12 Feb 2021 11:24:03 -0500 Subject: [PATCH 183/276] Bump v4.6.1 --- Makefile | 4 ++-- README.md | 2 +- bin/push-ccp-to-gcr.sh | 2 +- conf/postgres-operator/pgo.yaml | 4 ++-- docs/config.toml | 16 ++++++++-------- docs/content/Configuration/compatibility.md | 6 ++++++ docs/content/tutorial/pgbouncer.md | 4 ++-- examples/create-by-resource/fromcrd.json | 6 +++--- examples/envs.sh | 2 +- examples/helm/README.md | 4 ++-- examples/helm/postgres/Chart.yaml | 2 +- examples/helm/postgres/templates/pgcluster.yaml | 2 +- examples/helm/postgres/values.yaml | 2 +- examples/kustomize/createcluster/README.md | 16 ++++++++-------- .../kustomize/createcluster/base/pgcluster.yaml | 6 +++--- .../overlay/staging/hippo-rpl1-pgreplica.yaml | 2 +- installers/ansible/README.md | 2 +- installers/ansible/values.yaml | 6 +++--- installers/gcp-marketplace/Makefile | 2 +- installers/gcp-marketplace/README.md | 2 +- installers/gcp-marketplace/values.yaml | 6 +++--- installers/helm/Chart.yaml | 2 +- installers/helm/values.yaml | 6 +++--- installers/kubectl/client-setup.sh | 2 +- installers/kubectl/postgres-operator-ocp311.yml | 8 ++++---- installers/kubectl/postgres-operator.yml | 8 ++++---- installers/metrics/ansible/README.md | 2 +- installers/metrics/helm/Chart.yaml | 2 +- installers/metrics/helm/helm_template.yaml | 2 +- installers/metrics/helm/values.yaml | 2 +- .../kubectl/postgres-operator-metrics-ocp311.yml | 2 +- .../kubectl/postgres-operator-metrics.yml | 2 +- installers/olm/Makefile | 4 ++-- installers/olm/description.openshift.md | 2 +- installers/olm/description.upstream.md | 2 +- pkg/apis/crunchydata.com/v1/doc.go | 8 ++++---- pkg/apiservermsgs/common.go | 2 +- redhat/atomic/help.1 | 2 +- redhat/atomic/help.md | 2 +- 39 files changed, 82 insertions(+), 76 deletions(-) diff --git a/Makefile b/Makefile index a219cc2785..a97e454a80 100644 --- a/Makefile +++ b/Makefile @@ -5,9 +5,9 @@ PGOROOT ?= $(CURDIR) PGO_BASEOS ?= centos8 PGO_IMAGE_PREFIX ?= crunchydata PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) -PGO_VERSION ?= 4.6.0 +PGO_VERSION ?= 4.6.1 PGO_PG_VERSION ?= 13 -PGO_PG_FULLVERSION ?= 13.1 +PGO_PG_FULLVERSION ?= 13.2 PGO_BACKREST_VERSION ?= 2.31 PACKAGER ?= yum diff --git a/README.md b/README.md index 0ec169ebf5..1751080d53 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,7 @@ Based on your storage settings in your Kubernetes environment, you may be able t ```shell kubectl create namespace pgo -kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.0/installers/kubectl/postgres-operator.yml +kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.1/installers/kubectl/postgres-operator.yml ``` Otherwise, we highly recommend following the instructions from our [Quickstart](https://access.crunchydata.com/documentation/postgres-operator/latest/quickstart/). diff --git a/bin/push-ccp-to-gcr.sh b/bin/push-ccp-to-gcr.sh index 638fb13ab5..8528843b36 100755 --- a/bin/push-ccp-to-gcr.sh +++ b/bin/push-ccp-to-gcr.sh @@ -16,7 +16,7 @@ GCR_IMAGE_PREFIX=gcr.io/crunchy-dev-test CCP_IMAGE_PREFIX=crunchydata -CCP_IMAGE_TAG=centos8-13.1-4.6.0 +CCP_IMAGE_TAG=centos8-13.2-4.6.1 IMAGES=( crunchy-prometheus diff --git a/conf/postgres-operator/pgo.yaml b/conf/postgres-operator/pgo.yaml index 0b78876463..6b790b5435 100644 --- a/conf/postgres-operator/pgo.yaml +++ b/conf/postgres-operator/pgo.yaml @@ -2,7 +2,7 @@ Cluster: CCPImagePrefix: registry.developers.crunchydata.com/crunchydata Metrics: false Badger: false - CCPImageTag: centos8-13.1-4.6.0 + CCPImageTag: centos8-13.2-4.6.1 Port: 5432 PGBadgerPort: 10000 ExporterPort: 9187 @@ -81,4 +81,4 @@ Storage: Pgo: Audit: false PGOImagePrefix: registry.developers.crunchydata.com/crunchydata - PGOImageTag: centos8-4.6.0 + PGOImageTag: centos8-4.6.1 diff --git a/docs/config.toml b/docs/config.toml index 849b908b2a..e1aa049c8e 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -25,14 +25,14 @@ disableNavChevron = false # set true to hide next/prev chevron, default is false highlightClientSide = false # set true to use highlight.pack.js instead of the default hugo chroma highlighter menushortcutsnewtab = true # set true to open shortcuts links to a new tab/window enableGitInfo = true -operatorVersion = "4.6.0" -postgresVersion = "13.1" -postgresVersion13 = "13.1" -postgresVersion12 = "13.1" -postgresVersion11 = "11.10" -postgresVersion10 = "10.15" -postgresVersion96 = "9.6.20" -postgresVersion95 = "9.5.24" +operatorVersion = "4.6.1" +postgresVersion = "13.2" +postgresVersion13 = "13.2" +postgresVersion12 = "12.6" +postgresVersion11 = "11.11" +postgresVersion10 = "10.16" +postgresVersion96 = "9.6.21" +postgresVersion95 = "9.5.25" postgisVersion = "3.0" centosBase = "centos8" diff --git a/docs/content/Configuration/compatibility.md b/docs/content/Configuration/compatibility.md index b28901568f..abc74a23b5 100644 --- a/docs/content/Configuration/compatibility.md +++ b/docs/content/Configuration/compatibility.md @@ -12,6 +12,12 @@ version dependencies between the two projects. Below are the operator releases a | Operator Release | Container Release | Postgres | PgBackrest Version |:----------|:-------------|:------------|:-------------- +| 4.6.1 | 4.6.1 | 13.2 | 2.31 | +|||12.6|2.31| +|||11.11|2.31| +|||10.16|2.31| +|||9.6.21|2.31| +|||| | 4.6.0 | 4.6.0 | 13.1 | 2.31 | |||12.5|2.31| |||11.10|2.31| diff --git a/docs/content/tutorial/pgbouncer.md b/docs/content/tutorial/pgbouncer.md index a2d3b342c1..f8e5dd6364 100644 --- a/docs/content/tutorial/pgbouncer.md +++ b/docs/content/tutorial/pgbouncer.md @@ -116,7 +116,7 @@ PGPASSWORD=randompassword psql -h localhost -p 5432 -U pgbouncer pgbouncer You should see something similar to this: ``` -psql (13.1, server 1.14.0/bouncer) +psql ({{< param postgresVersion >}}, server 1.14.0/bouncer) Type "help" for help. pgbouncer=# @@ -172,7 +172,7 @@ PGPASSWORD=securerandomlygeneratedpassword psql -h localhost -p 5432 -U testuser You should see something similar to this: ``` -psql (13.1) +psql ({{< param postgresVersion >}}) SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off) Type "help" for help. diff --git a/examples/create-by-resource/fromcrd.json b/examples/create-by-resource/fromcrd.json index 9483cf2adb..6b8cb41f29 100644 --- a/examples/create-by-resource/fromcrd.json +++ b/examples/create-by-resource/fromcrd.json @@ -10,7 +10,7 @@ "deployment-name": "fromcrd", "name": "fromcrd", "pg-cluster": "fromcrd", - "pgo-version": "4.6.0", + "pgo-version": "4.6.1", "pgouser": "pgoadmin" }, "name": "fromcrd", @@ -45,7 +45,7 @@ "supplementalgroups": "" }, "ccpimage": "crunchy-postgres-ha", - "ccpimagetag": "centos8-13.1-4.6.0", + "ccpimagetag": "centos8-13.2-4.6.1", "clustername": "fromcrd", "database": "userdb", "exporterport": "9187", @@ -60,7 +60,7 @@ "port": "5432", "user": "testuser", "userlabels": { - "pgo-version": "4.6.0" + "pgo-version": "4.6.1" } } } diff --git a/examples/envs.sh b/examples/envs.sh index 5788af2eb3..43712b0f97 100644 --- a/examples/envs.sh +++ b/examples/envs.sh @@ -20,7 +20,7 @@ export PGO_CONF_DIR=$PGOROOT/installers/ansible/roles/pgo-operator/files # the version of the Operator you run is set by these vars export PGO_IMAGE_PREFIX=registry.developers.crunchydata.com/crunchydata export PGO_BASEOS=centos8 -export PGO_VERSION=4.6.0 +export PGO_VERSION=4.6.1 export PGO_IMAGE_TAG=$PGO_BASEOS-$PGO_VERSION # for setting the pgo apiserver port, disabling TLS or not verifying TLS diff --git a/examples/helm/README.md b/examples/helm/README.md index c99a3e58d0..f1fd384bc1 100644 --- a/examples/helm/README.md +++ b/examples/helm/README.md @@ -64,7 +64,7 @@ The following values can also be set: - `ha`: Whether or not to deploy a high availability PostgreSQL cluster. Can be either `true` or `false`, defaults to `false`. - `imagePrefix`: The prefix of the container images to use for this PostgreSQL cluster. Default to `registry.developers.crunchydata.com/crunchydata`. - `image`: The name of the container image to use for the PostgreSQL cluster. Defaults to `crunchy-postgres-ha`. -- `imageTag`: The container image tag to use. Defaults to `centos8-13.1-4.6.0`. +- `imageTag`: The container image tag to use. Defaults to `centos8-13.2-4.6.1`. - `memory`: The memory limit for the PostgreSQL cluster. Follows standard Kubernetes formatting. - `monitoring`: Whether or not to enable monitoring / metrics collection for this PostgreSQL instance. Can either be `true` or `false`, defaults to `false`. @@ -103,7 +103,7 @@ PGPASSWORD="W4tch0ut4hippo$" psql -h localhost -U hippo hippo ## Notes -Prior to PostgreSQL Operator 4.6.0, you will have to manually clean up some of the artifacts when running `helm uninstall`. +Prior to PostgreSQL Operator 4.6.1, you will have to manually clean up some of the artifacts when running `helm uninstall`. ## Additional Resources diff --git a/examples/helm/postgres/Chart.yaml b/examples/helm/postgres/Chart.yaml index 60501fc9a7..d61868c39e 100644 --- a/examples/helm/postgres/Chart.yaml +++ b/examples/helm/postgres/Chart.yaml @@ -20,4 +20,4 @@ version: 0.2.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. -appVersion: 4.6.0 +appVersion: 4.6.1 diff --git a/examples/helm/postgres/templates/pgcluster.yaml b/examples/helm/postgres/templates/pgcluster.yaml index c4d71c51c2..1f9097e7dd 100644 --- a/examples/helm/postgres/templates/pgcluster.yaml +++ b/examples/helm/postgres/templates/pgcluster.yaml @@ -28,7 +28,7 @@ spec: storagetype: dynamic ccpimage: {{ .Values.image | default "crunchy-postgres-ha" | quote }} ccpimageprefix: {{ .Values.imagePrefix | default "registry.developers.crunchydata.com/crunchydata" | quote }} - ccpimagetag: {{ .Values.imageTag | default "centos8-13.1-4.6.0" | quote }} + ccpimagetag: {{ .Values.imageTag | default "centos8-13.2-4.6.1" | quote }} clustername: {{ .Values.name | quote }} database: {{ .Values.name | quote }} {{- if .Values.monitoring }} diff --git a/examples/helm/postgres/values.yaml b/examples/helm/postgres/values.yaml index ac247df8db..e5041f55de 100644 --- a/examples/helm/postgres/values.yaml +++ b/examples/helm/postgres/values.yaml @@ -10,5 +10,5 @@ password: W4tch0ut4hippo$ # ha: true # imagePrefix: registry.developers.crunchydata.com/crunchydata # image: crunchy-postgres-ha -# imageTag: centos8-13.1-4.6.0 +# imageTag: centos8-13.2-4.6.1 # memory: 1Gi diff --git a/examples/kustomize/createcluster/README.md b/examples/kustomize/createcluster/README.md index 0b35a3ae22..9ddb3cd458 100644 --- a/examples/kustomize/createcluster/README.md +++ b/examples/kustomize/createcluster/README.md @@ -44,13 +44,13 @@ pgo show cluster hippo -n pgo ``` You will see something like this if successful: ``` -cluster : hippo (crunchy-postgres-ha:centos8-13.1-4.6.0) +cluster : hippo (crunchy-postgres-ha:centos8-13.2-4.6.1) pod : hippo-8fb6bd96-j87wq (Running) on gke-xxxx-default-pool-38e946bd-257w (1/1) (primary) pvc: hippo (1Gi) deployment : hippo deployment : hippo-backrest-shared-repo service : hippo - ClusterIP (10.0.56.86) - Ports (2022/TCP, 5432/TCP) - labels : pgo-version=4.6.0 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.1 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata ``` Feel free to run other pgo cli commands on the hippo cluster @@ -79,7 +79,7 @@ pgo show cluster dev-hippo -n pgo ``` You will see something like this if successful: ``` -cluster : dev-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0) +cluster : dev-hippo (crunchy-postgres-ha:centos8-13.2-4.6.1) pod : dev-hippo-588d4cb746-bwrxb (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: dev-hippo (1Gi) deployment : dev-hippo @@ -87,7 +87,7 @@ cluster : dev-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0) deployment : dev-hippo-pgbouncer service : dev-hippo - ClusterIP (10.0.62.87) - Ports (2022/TCP, 5432/TCP) service : dev-hippo-pgbouncer - ClusterIP (10.0.48.120) - Ports (5432/TCP) - labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.6.0 pgouser=admin + labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.6.1 pgouser=admin ``` #### staging The staging overlay will deploy a crunchy postgreSQL cluster with 2 replica's with annotations added @@ -113,7 +113,7 @@ pgo show cluster staging-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : staging-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0) +cluster : staging-hippo (crunchy-postgres-ha:centos8-13.2-4.6.1) pod : staging-hippo-85cf6dcb65-9h748 (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: staging-hippo (1Gi) pod : staging-hippo-lnxw-cf47d8c8b-6r4wn (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (replica) @@ -128,7 +128,7 @@ cluster : staging-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0) service : staging-hippo-replica - ClusterIP (10.0.56.57) - Ports (2022/TCP, 5432/TCP) pgreplica : staging-hippo-lnxw pgreplica : staging-hippo-rpl1 - labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.6.0 pgouser=admin vendor=crunchydata + labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.6.1 pgouser=admin vendor=crunchydata ``` #### production @@ -154,7 +154,7 @@ pgo show cluster prod-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : prod-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0) +cluster : prod-hippo (crunchy-postgres-ha:centos8-13.2-4.6.1) pod : prod-hippo-5d6dd46497-rr67c (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (primary) pvc: prod-hippo (1Gi) pod : prod-hippo-flty-84d97c8769-2pzbh (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (replica) @@ -165,7 +165,7 @@ cluster : prod-hippo (crunchy-postgres-ha:centos8-13.1-4.6.0) service : prod-hippo - ClusterIP (10.0.56.18) - Ports (2022/TCP, 5432/TCP) service : prod-hippo-replica - ClusterIP (10.0.56.101) - Ports (2022/TCP, 5432/TCP) pgreplica : prod-hippo-flty - labels : pgo-version=4.6.0 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.1 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata ``` ### Delete the clusters To delete the clusters run the following pgo cli commands diff --git a/examples/kustomize/createcluster/base/pgcluster.yaml b/examples/kustomize/createcluster/base/pgcluster.yaml index 28fbf7f49d..1f9773dfb6 100644 --- a/examples/kustomize/createcluster/base/pgcluster.yaml +++ b/examples/kustomize/createcluster/base/pgcluster.yaml @@ -10,7 +10,7 @@ metadata: deployment-name: hippo name: hippo pg-cluster: hippo - pgo-version: 4.6.0 + pgo-version: 4.6.1 pgouser: admin name: hippo namespace: pgo @@ -46,7 +46,7 @@ spec: postgres: {} ccpimage: crunchy-postgres-ha ccpimageprefix: registry.developers.crunchydata.com/crunchydata - ccpimagetag: centos8-13.1-4.6.0 + ccpimagetag: centos8-13.2-4.6.1 clustername: hippo customconfig: "" database: hippo @@ -69,4 +69,4 @@ spec: port: "5432" user: hippo userlabels: - pgo-version: 4.6.0 + pgo-version: 4.6.1 diff --git a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml index b9904e2cb8..58d8fead81 100644 --- a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml +++ b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml @@ -20,4 +20,4 @@ spec: storagetype: dynamic supplementalgroups: "" userlabels: - pgo-version: 4.6.0 + pgo-version: 4.6.1 diff --git a/installers/ansible/README.md b/installers/ansible/README.md index a7be44259a..b41072a007 100644 --- a/installers/ansible/README.md +++ b/installers/ansible/README.md @@ -4,7 +4,7 @@ Crunchy Data

-Latest Release: 4.6.0 +Latest Release: 4.6.1 ## General diff --git a/installers/ansible/values.yaml b/installers/ansible/values.yaml index d108a194f9..58007a3d36 100644 --- a/installers/ansible/values.yaml +++ b/installers/ansible/values.yaml @@ -17,7 +17,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.1-4.6.0" +ccp_image_tag: "centos8-13.2-4.6.1" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -50,14 +50,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.0" +pgo_client_version: "4.6.1" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos8-4.6.0" +pgo_image_tag: "centos8-4.6.1" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/gcp-marketplace/Makefile b/installers/gcp-marketplace/Makefile index 21c0a54376..060c017ff8 100644 --- a/installers/gcp-marketplace/Makefile +++ b/installers/gcp-marketplace/Makefile @@ -6,7 +6,7 @@ MARKETPLACE_TOOLS ?= gcr.io/cloud-marketplace-tools/k8s/dev:$(MARKETPLACE_VERSIO MARKETPLACE_VERSION ?= 0.9.4 KUBECONFIG ?= $(HOME)/.kube/config PARAMETERS ?= {} -PGO_VERSION ?= 4.6.0 +PGO_VERSION ?= 4.6.1 IMAGE_BUILD_ARGS = --build-arg MARKETPLACE_VERSION='$(MARKETPLACE_VERSION)' \ --build-arg PGO_VERSION='$(PGO_VERSION)' diff --git a/installers/gcp-marketplace/README.md b/installers/gcp-marketplace/README.md index b079936d64..13f4859557 100644 --- a/installers/gcp-marketplace/README.md +++ b/installers/gcp-marketplace/README.md @@ -59,7 +59,7 @@ Google Cloud Marketplace. ```shell IMAGE_REPOSITORY=gcr.io/crunchydata-public/postgres-operator - export PGO_VERSION=4.6.0 + export PGO_VERSION=4.6.1 export INSTALLER_IMAGE=${IMAGE_REPOSITORY}/deployer:${PGO_VERSION} export OPERATOR_IMAGE=${IMAGE_REPOSITORY}:${PGO_VERSION} export OPERATOR_IMAGE_API=${IMAGE_REPOSITORY}/pgo-apiserver:${PGO_VERSION} diff --git a/installers/gcp-marketplace/values.yaml b/installers/gcp-marketplace/values.yaml index 3b44d04e88..f2722de252 100644 --- a/installers/gcp-marketplace/values.yaml +++ b/installers/gcp-marketplace/values.yaml @@ -10,7 +10,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.1-4.6.0" +ccp_image_tag: "centos8-13.2-4.6.1" create_rbac: "true" db_name: "" db_password_age_days: "0" @@ -32,9 +32,9 @@ pgo_admin_role_name: "pgoadmin" pgo_admin_username: "admin" pgo_client_container_install: "false" pgo_client_install: 'false' -pgo_client_version: "4.6.0" +pgo_client_version: "4.6.1" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.0" +pgo_image_tag: "centos8-4.6.1" pgo_installation_name: '${OPERATOR_NAME}' pgo_operator_namespace: '${OPERATOR_NAMESPACE}' scheduler_timeout: "3600" diff --git a/installers/helm/Chart.yaml b/installers/helm/Chart.yaml index fcb7fba745..bc1f55c408 100644 --- a/installers/helm/Chart.yaml +++ b/installers/helm/Chart.yaml @@ -3,7 +3,7 @@ name: postgres-operator description: Crunchy PostgreSQL Operator Helm chart for Kubernetes type: application version: 0.2.0 -appVersion: 4.6.0 +appVersion: 4.6.1 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/crunchy_logo.png keywords: diff --git a/installers/helm/values.yaml b/installers/helm/values.yaml index 33f85fed19..1ff43f22ac 100644 --- a/installers/helm/values.yaml +++ b/installers/helm/values.yaml @@ -37,7 +37,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.1-4.6.0" +ccp_image_tag: "centos8-13.2-4.6.1" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -70,14 +70,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.0" +pgo_client_version: "4.6.1" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos8-4.6.0" +pgo_image_tag: "centos8-4.6.1" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/kubectl/client-setup.sh b/installers/kubectl/client-setup.sh index 4f8c216a64..ac82ddddc9 100755 --- a/installers/kubectl/client-setup.sh +++ b/installers/kubectl/client-setup.sh @@ -14,7 +14,7 @@ # This script should be run after the operator has been deployed PGO_OPERATOR_NAMESPACE="${PGO_OPERATOR_NAMESPACE:-pgo}" PGO_USER_ADMIN="${PGO_USER_ADMIN:-pgouser-admin}" -PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.0}" +PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.1}" PGO_CLIENT_URL="https://github.com/CrunchyData/postgres-operator/releases/download/${PGO_CLIENT_VERSION}" PGO_CMD="${PGO_CMD-kubectl}" diff --git a/installers/kubectl/postgres-operator-ocp311.yml b/installers/kubectl/postgres-operator-ocp311.yml index df05020bc2..46cc48774c 100644 --- a/installers/kubectl/postgres-operator-ocp311.yml +++ b/installers/kubectl/postgres-operator-ocp311.yml @@ -44,7 +44,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos8-13.1-4.6.0" + ccp_image_tag: "centos8-13.2-4.6.1" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -77,14 +77,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.0" + pgo_client_version: "4.6.1" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos8-4.6.0" + pgo_image_tag: "centos8-4.6.1" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -161,7 +161,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.1 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/kubectl/postgres-operator.yml b/installers/kubectl/postgres-operator.yml index ca57f5c380..87a144b7a0 100644 --- a/installers/kubectl/postgres-operator.yml +++ b/installers/kubectl/postgres-operator.yml @@ -139,7 +139,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos8-13.1-4.6.0" + ccp_image_tag: "centos8-13.2-4.6.1" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -172,14 +172,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.0" + pgo_client_version: "4.6.1" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos8-4.6.0" + pgo_image_tag: "centos8-4.6.1" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -269,7 +269,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.1 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/ansible/README.md b/installers/metrics/ansible/README.md index 5b2e93ad52..1a0c731b73 100644 --- a/installers/metrics/ansible/README.md +++ b/installers/metrics/ansible/README.md @@ -4,7 +4,7 @@ Crunchy Data

-Latest Release: 4.6.0 +Latest Release: 4.6.1 ## General diff --git a/installers/metrics/helm/Chart.yaml b/installers/metrics/helm/Chart.yaml index 8155045d2e..44296af626 100644 --- a/installers/metrics/helm/Chart.yaml +++ b/installers/metrics/helm/Chart.yaml @@ -3,6 +3,6 @@ name: postgres-operator-monitoring description: Install for Crunchy PostgreSQL Operator Monitoring type: application version: 0.2.0 -appVersion: 4.6.0 +appVersion: 4.6.1 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/crunchy_logo.png diff --git a/installers/metrics/helm/helm_template.yaml b/installers/metrics/helm/helm_template.yaml index 718a29901f..bcf1fca573 100644 --- a/installers/metrics/helm/helm_template.yaml +++ b/installers/metrics/helm/helm_template.yaml @@ -20,5 +20,5 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.0" +pgo_image_tag: "centos8-4.6.1" diff --git a/installers/metrics/helm/values.yaml b/installers/metrics/helm/values.yaml index f7a9300d19..f0461cbadc 100644 --- a/installers/metrics/helm/values.yaml +++ b/installers/metrics/helm/values.yaml @@ -20,7 +20,7 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.0" +pgo_image_tag: "centos8-4.6.1" # ===================== # Configuration Options diff --git a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml index edb7a514b1..12dbdd62ef 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml @@ -96,7 +96,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.1 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/kubectl/postgres-operator-metrics.yml b/installers/metrics/kubectl/postgres-operator-metrics.yml index 382b0a7259..071e3b6331 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics.yml @@ -165,7 +165,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.0 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.1 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/olm/Makefile b/installers/olm/Makefile index 7d513b7452..23d4e140bd 100644 --- a/installers/olm/Makefile +++ b/installers/olm/Makefile @@ -2,7 +2,7 @@ .SUFFIXES: CCP_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -CCP_PG_FULLVERSION ?= 13.1 +CCP_PG_FULLVERSION ?= 13.2 CCP_POSTGIS_VERSION ?= 3.0 CONTAINER ?= docker KUBECONFIG ?= $(HOME)/.kube/config @@ -11,7 +11,7 @@ OLM_TOOLS ?= registry.localhost:5000/postgres-operator-olm-tools:$(OLM_SDK_VERSI OLM_VERSION ?= 0.15.1 PGO_BASEOS ?= centos8 PGO_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -PGO_VERSION ?= 4.6.0 +PGO_VERSION ?= 4.6.1 PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) CCP_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(PGO_VERSION) CCP_POSTGIS_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(CCP_POSTGIS_VERSION)-$(PGO_VERSION) diff --git a/installers/olm/description.openshift.md b/installers/olm/description.openshift.md index fe9cee374e..ca018e2cfa 100644 --- a/installers/olm/description.openshift.md +++ b/installers/olm/description.openshift.md @@ -187,7 +187,7 @@ spec: annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: ${cluster_image_prefix} - ccpimagetag: centos8-13.1-${PGO_VERSION} + ccpimagetag: centos8-13.2-${PGO_VERSION} clustername: ${pgo_cluster_name} database: ${pgo_cluster_name} exporterport: "9187" diff --git a/installers/olm/description.upstream.md b/installers/olm/description.upstream.md index bbad9621a7..989928f8b2 100644 --- a/installers/olm/description.upstream.md +++ b/installers/olm/description.upstream.md @@ -167,7 +167,7 @@ spec: annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: ${cluster_image_prefix} - ccpimagetag: centos8-13.1-${PGO_VERSION} + ccpimagetag: centos8-13.2-${PGO_VERSION} clustername: ${pgo_cluster_name} database: ${pgo_cluster_name} exporterport: "9187" diff --git a/pkg/apis/crunchydata.com/v1/doc.go b/pkg/apis/crunchydata.com/v1/doc.go index c02da94ccc..14cf9279a7 100644 --- a/pkg/apis/crunchydata.com/v1/doc.go +++ b/pkg/apis/crunchydata.com/v1/doc.go @@ -53,7 +53,7 @@ cluster. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.0", + '{"ClientVersion":"4.6.1", "Namespace":"pgouser1", "Name":"mycluster", $PGO_APISERVER_URL/clusters @@ -72,7 +72,7 @@ show all of the clusters that are in the given namespace. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.0", + '{"ClientVersion":"4.6.1", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/showclusters @@ -82,7 +82,7 @@ $PGO_APISERVER_URL/showclusters curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.0", + '{"ClientVersion":"4.6.1", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/clustersdelete @@ -90,7 +90,7 @@ $PGO_APISERVER_URL/clustersdelete Schemes: http, https BasePath: / - Version: 4.6.0 + Version: 4.6.1 License: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0 Contact: Crunchy Data https://www.crunchydata.com/ diff --git a/pkg/apiservermsgs/common.go b/pkg/apiservermsgs/common.go index b4acfbd6ba..f312e0fff7 100644 --- a/pkg/apiservermsgs/common.go +++ b/pkg/apiservermsgs/common.go @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -const PGO_VERSION = "4.6.0" +const PGO_VERSION = "4.6.1" // Ok status const Ok = "ok" diff --git a/redhat/atomic/help.1 b/redhat/atomic/help.1 index 6f16a1c76d..f7b98114d3 100644 --- a/redhat/atomic/help.1 +++ b/redhat/atomic/help.1 @@ -56,4 +56,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa \fB\fCRelease=\fR .PP -The specific release number of the container. For example, Release="4.6.0" +The specific release number of the container. For example, Release="4.6.1" diff --git a/redhat/atomic/help.md b/redhat/atomic/help.md index b156931e07..0e013b84a0 100644 --- a/redhat/atomic/help.md +++ b/redhat/atomic/help.md @@ -45,4 +45,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa `Release=` -The specific release number of the container. For example, Release="4.6.0" +The specific release number of the container. For example, Release="4.6.1" From 832eef9a681f92578a35d03652d960a586cd9795 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Fri, 12 Feb 2021 11:29:57 -0500 Subject: [PATCH 184/276] 4.6.1 release notes --- docs/content/releases/4.6.1.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 docs/content/releases/4.6.1.md diff --git a/docs/content/releases/4.6.1.md b/docs/content/releases/4.6.1.md new file mode 100644 index 0000000000..6019e50aa5 --- /dev/null +++ b/docs/content/releases/4.6.1.md @@ -0,0 +1,32 @@ +--- +title: "4.6.1" +date: +draft: false +weight: 59 +--- + +Crunchy Data announces the release of the PostgreSQL Operator 4.5.1 on November 13, 2020. + +The PostgreSQL Operator is released in conjunction with the [Crunchy Container Suite](https://github.com/CrunchyData/crunchy-containers/). + +PostgreSQL Operator 4.6.1 release includes the following software versions upgrades: + +- [PostgreSQL](https://www.postgresql.org) is now at versions 13.2, 12.6, 11.11, 10.16, and 9.6.21. + +PostgreSQL Operator is tested against Kubernetes 1.17 - 1.20, OpenShift 3.11, OpenShift 4.4+, Google Kubernetes Engine (GKE), Amazon EKS, Microsoft AKS, and VMware Enterprise PKS 1.3+, and works on other Kubernetes distributions as well. + +# Changes +- The `--compress-type` flag is now supported for the backup options (`--backup-opts`) for pgBackRest backups with `pgo backup`. `none`, `gz`, `bz2`, and `lz4` are all supported. Presently `zst` is not supported. +- The post-cluster creation pgBackRest tasks, i.e. creating a stanza and creating an initial backup, are now deleted by the Operator should they complete successfully. Besides good housekeeping, this provides a workaround for an OpenShift 3.11 bug that was causing the Operator to continuously issue pgBackRest backups during an OpenShift refresh cycle. Reported by Paul Heinen (@v3nturetheworld). + +# Fixes +- Only attempts to start scheduled backups in running pgBackRest repository Pods. Reported by Satria Sahputra (@satriashp). +- Support the substitution for the limit on the number of queries to include the the `pg_stat_statements` support of pgMonitor. Defaults to 20, which is the pgMonitor upstream value. Contributed by Steven Siahetiong (@ssiahetiong). +- On initialization, check that primary PostgreSQL Deployment is created before attempting to scale. +- Fix issue with `pgo backup` where it was unable to take a backup from a new primary after `pgo failover` was called. Reported by (@mesobreira). +- Fix crash when attempting to create a cluster via the REST API and no custom labels were set. Reported by Jeffrey den Drijver (@JeffreyDD) +- Ensure a pgBadger sidecar is not added to a PostgreSQL cluster after a `pgo upgrade` if it was not previously specified. +- Ensure superfluous labels are deleted during a `pgo upgrade`. +- Remove `/crunchyadm` from `unix_socket_directories` configuration during a `pgo upgrade`. Reported by Steven Siahetiong (@ssiahetiong). +- Ensure updated paths, i.e. rename to `/opt/crunchy`, are reflected in cluster ConfigMap when running `pgo upgrade`. Reported by Steven Siahetiong (@ssiahetiong). +- Ensure value from `--ccp-image-tag` is applied when running `pgo upgrade`. From 0a236785ce1b56c14b7f308d89bc3be20853e2b6 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Fri, 12 Feb 2021 11:31:06 -0500 Subject: [PATCH 185/276] Update date in release notes --- docs/content/releases/4.6.1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/releases/4.6.1.md b/docs/content/releases/4.6.1.md index 6019e50aa5..2fabbf5dfa 100644 --- a/docs/content/releases/4.6.1.md +++ b/docs/content/releases/4.6.1.md @@ -5,7 +5,7 @@ draft: false weight: 59 --- -Crunchy Data announces the release of the PostgreSQL Operator 4.5.1 on November 13, 2020. +Crunchy Data announces the release of the PostgreSQL Operator 4.6.1 on February 15, 2021. The PostgreSQL Operator is released in conjunction with the [Crunchy Container Suite](https://github.com/CrunchyData/crunchy-containers/). From 8867cfeafb9d446364296b573d28c0cedb01ee39 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Fri, 12 Feb 2021 15:32:02 -0500 Subject: [PATCH 186/276] Revise inernal references in pkg components The location of these functions caused a cross internal/pkg reference, which made external SDK builds difficult. This removes the internal components from the pkg components to ensure proper pkg compatibility. --- cmd/pgo/cmd/user.go | 5 +- internal/apiserver/common.go | 25 +++++++++ internal/apiserver/common_test.go | 42 +++++++++++++++ internal/apiserver/userservice/userimpl.go | 8 +-- pkg/apiservermsgs/configmsgs.go | 6 +-- pkg/apiservermsgs/usermsgs.go | 31 ----------- pkg/apiservermsgs/usermsgs_test.go | 63 ---------------------- pkg/events/eventing.go | 3 -- 8 files changed, 75 insertions(+), 108 deletions(-) delete mode 100644 pkg/apiservermsgs/usermsgs_test.go diff --git a/cmd/pgo/cmd/user.go b/cmd/pgo/cmd/user.go index bc8f9352ae..59d6cb1142 100644 --- a/cmd/pgo/cmd/user.go +++ b/cmd/pgo/cmd/user.go @@ -22,6 +22,7 @@ import ( "github.com/crunchydata/postgres-operator/cmd/pgo/api" "github.com/crunchydata/postgres-operator/cmd/pgo/util" + "github.com/crunchydata/postgres-operator/internal/apiserver" utiloperator "github.com/crunchydata/postgres-operator/internal/util" msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs" @@ -89,7 +90,7 @@ func createUser(args []string, ns string) { } // determine if the user provies a valid password type - if _, err := msgs.GetPasswordType(PasswordType); err != nil { + if _, err := apiserver.GetPasswordType(PasswordType); err != nil { fmt.Println("Error:", err.Error()) os.Exit(1) } @@ -400,7 +401,7 @@ func updateUser(clusterNames []string, namespace string) { } // determine if the user provies a valid password type - if _, err := msgs.GetPasswordType(PasswordType); err != nil { + if _, err := apiserver.GetPasswordType(PasswordType); err != nil { fmt.Println("Error:", err.Error()) os.Exit(1) } diff --git a/internal/apiserver/common.go b/internal/apiserver/common.go index 50e0db963a..d58201b276 100644 --- a/internal/apiserver/common.go +++ b/internal/apiserver/common.go @@ -21,6 +21,7 @@ import ( "fmt" "strings" + pgpassword "github.com/crunchydata/postgres-operator/internal/postgres/password" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" log "github.com/sirupsen/logrus" @@ -44,6 +45,9 @@ var ( // ErrDBContainerNotFound is an error that indicates that a "database" container // could not be found in a specific pod ErrDBContainerNotFound = errors.New("\"database\" container not found in pod") + // ErrPasswordTypeInvalid is used when a string that's not included in + // PasswordTypeStrings is used + ErrPasswordTypeInvalid = errors.New("invalid password type. choices are (md5, scram-sha-256)") // ErrStandbyNotAllowed contains the error message returned when an API call is not // permitted because it involves a cluster that is in standby mode ErrStandbyNotAllowed = errors.New("Action not permitted because standby mode is enabled") @@ -54,6 +58,27 @@ var ( "Operator installation") ) +// passwordTypeStrings is a mapping of strings of password types to their +// corresponding value of the structured password type +var passwordTypeStrings = map[string]pgpassword.PasswordType{ + "": pgpassword.MD5, + "md5": pgpassword.MD5, + "scram": pgpassword.SCRAM, + "scram-sha-256": pgpassword.SCRAM, +} + +// GetPasswordType returns the enumerated password type based on the string, and +// an error if it cannot match one +func GetPasswordType(passwordTypeStr string) (pgpassword.PasswordType, error) { + passwordType, ok := passwordTypeStrings[passwordTypeStr] + + if !ok { + return passwordType, ErrPasswordTypeInvalid + } + + return passwordType, nil +} + // IsValidPVC determines if a PVC with the name provided exits func IsValidPVC(pvcName, ns string) bool { ctx := context.TODO() diff --git a/internal/apiserver/common_test.go b/internal/apiserver/common_test.go index 9f11dc4e49..7b31071603 100644 --- a/internal/apiserver/common_test.go +++ b/internal/apiserver/common_test.go @@ -16,13 +16,55 @@ limitations under the License. */ import ( + "errors" "testing" + pgpassword "github.com/crunchydata/postgres-operator/internal/postgres/password" crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1" "k8s.io/apimachinery/pkg/api/resource" ) +func TestGetPasswordType(t *testing.T) { + t.Run("valid", func(t *testing.T) { + tests := map[string]pgpassword.PasswordType{ + "": pgpassword.MD5, + "md5": pgpassword.MD5, + "scram": pgpassword.SCRAM, + "scram-sha-256": pgpassword.SCRAM, + } + + for passwordTypeStr, expected := range tests { + t.Run(passwordTypeStr, func(t *testing.T) { + passwordType, err := GetPasswordType(passwordTypeStr) + if err != nil { + t.Error(err) + return + } + + if passwordType != expected { + t.Errorf("password type %q should yield %d", passwordTypeStr, expected) + } + }) + } + }) + + t.Run("invalid", func(t *testing.T) { + tests := map[string]error{ + "magic": ErrPasswordTypeInvalid, + "scram-sha-512": ErrPasswordTypeInvalid, + } + + for passwordTypeStr, expected := range tests { + t.Run(passwordTypeStr, func(t *testing.T) { + if _, err := GetPasswordType(passwordTypeStr); !errors.Is(err, expected) { + t.Errorf("password type %q should yield error %q", passwordTypeStr, expected.Error()) + } + }) + } + }) +} + func TestValidateBackrestStorageTypeForCommand(t *testing.T) { cluster := &crv1.Pgcluster{ Spec: crv1.PgclusterSpec{}, diff --git a/internal/apiserver/userservice/userimpl.go b/internal/apiserver/userservice/userimpl.go index 8c919859b4..9f7998d6ed 100644 --- a/internal/apiserver/userservice/userimpl.go +++ b/internal/apiserver/userservice/userimpl.go @@ -155,7 +155,7 @@ func CreateUser(request *msgs.CreateUserRequest, pgouser string) msgs.CreateUser } // determine if the user passed in a valid password type - passwordType, err := msgs.GetPasswordType(request.PasswordType) + passwordType, err := apiserver.GetPasswordType(request.PasswordType) if err != nil { response.Status.Code = msgs.Error response.Status.Msg = err.Error() @@ -601,7 +601,7 @@ func UpdateUser(request *msgs.UpdateUserRequest, pgouser string) msgs.UpdateUser } // determine if the user passed in a valid password type - if _, err := msgs.GetPasswordType(request.PasswordType); err != nil { + if _, err := apiserver.GetPasswordType(request.PasswordType); err != nil { response.Status.Code = msgs.Error response.Status.Msg = err.Error() return response @@ -940,7 +940,7 @@ func rotateExpiredPasswords(request *msgs.UpdateUserRequest, cluster *crv1.Pgclu // get the password type. the error is already evaluated in a called // function - passwordType, _ := msgs.GetPasswordType(request.PasswordType) + passwordType, _ := apiserver.GetPasswordType(request.PasswordType) // generate a new password. Check to see if the user passed in a particular // length of the password, or passed in a password to rotate (though that @@ -1072,7 +1072,7 @@ func updateUser(request *msgs.UpdateUserRequest, cluster *crv1.Pgcluster) msgs.U // Speaking of passwords...let's first determine if the user updated their // password. See generatePassword for how precedence is given for password // updates - passwordType, _ := msgs.GetPasswordType(request.PasswordType) + passwordType, _ := apiserver.GetPasswordType(request.PasswordType) isChanged, password, hashedPassword, err := generatePassword(result.Username, request.Password, passwordType, request.RotatePassword, request.PasswordLength) // in the off-chance there is an error generating the password, record it diff --git a/pkg/apiservermsgs/configmsgs.go b/pkg/apiservermsgs/configmsgs.go index 325e2281e5..1a4f8aae9c 100644 --- a/pkg/apiservermsgs/configmsgs.go +++ b/pkg/apiservermsgs/configmsgs.go @@ -15,13 +15,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -import ( - "github.com/crunchydata/postgres-operator/internal/config" -) - // ShowConfigResponse ... // swagger:model type ShowConfigResponse struct { - Result config.PgoConfig + Result interface{} Status } diff --git a/pkg/apiservermsgs/usermsgs.go b/pkg/apiservermsgs/usermsgs.go index 5de550711c..276f35152f 100644 --- a/pkg/apiservermsgs/usermsgs.go +++ b/pkg/apiservermsgs/usermsgs.go @@ -15,12 +15,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import ( - "errors" - - pgpassword "github.com/crunchydata/postgres-operator/internal/postgres/password" -) - type UpdateClusterLoginState int // set the different values around whether or not to disable/enable a user's @@ -31,19 +25,6 @@ const ( UpdateUserLoginDisable ) -// ErrPasswordTypeInvalid is used when a string that's not included in -// PasswordTypeStrings is used -var ErrPasswordTypeInvalid = errors.New("invalid password type. choices are (md5, scram-sha-256)") - -// passwordTypeStrings is a mapping of strings of password types to their -// corresponding value of the structured password type -var passwordTypeStrings = map[string]pgpassword.PasswordType{ - "": pgpassword.MD5, - "md5": pgpassword.MD5, - "scram": pgpassword.SCRAM, - "scram-sha-256": pgpassword.SCRAM, -} - // CreateUserRequest contains the parameters that are passed in when an Operator // user requests to create a new PostgreSQL user // swagger:model @@ -153,15 +134,3 @@ type UserResponseDetail struct { Username string ValidUntil string } - -// GetPasswordType returns the enumerated password type based on the string, and -// an error if it cannot match one -func GetPasswordType(passwordTypeStr string) (pgpassword.PasswordType, error) { - passwordType, ok := passwordTypeStrings[passwordTypeStr] - - if !ok { - return passwordType, ErrPasswordTypeInvalid - } - - return passwordType, nil -} diff --git a/pkg/apiservermsgs/usermsgs_test.go b/pkg/apiservermsgs/usermsgs_test.go deleted file mode 100644 index d6cc93b2ca..0000000000 --- a/pkg/apiservermsgs/usermsgs_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package apiservermsgs - -/* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. - 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. -*/ - -import ( - "errors" - "testing" - - pgpassword "github.com/crunchydata/postgres-operator/internal/postgres/password" -) - -func TestGetPasswordType(t *testing.T) { - t.Run("valid", func(t *testing.T) { - tests := map[string]pgpassword.PasswordType{ - "": pgpassword.MD5, - "md5": pgpassword.MD5, - "scram": pgpassword.SCRAM, - "scram-sha-256": pgpassword.SCRAM, - } - - for passwordTypeStr, expected := range tests { - t.Run(passwordTypeStr, func(t *testing.T) { - passwordType, err := GetPasswordType(passwordTypeStr) - if err != nil { - t.Error(err) - return - } - - if passwordType != expected { - t.Errorf("password type %q should yield %d", passwordTypeStr, expected) - } - }) - } - }) - - t.Run("invalid", func(t *testing.T) { - tests := map[string]error{ - "magic": ErrPasswordTypeInvalid, - "scram-sha-512": ErrPasswordTypeInvalid, - } - - for passwordTypeStr, expected := range tests { - t.Run(passwordTypeStr, func(t *testing.T) { - if _, err := GetPasswordType(passwordTypeStr); !errors.Is(err, expected) { - t.Errorf("password type %q should yield error %q", passwordTypeStr, expected.Error()) - } - }) - } - }) -} diff --git a/pkg/events/eventing.go b/pkg/events/eventing.go index 1a13b932f1..d3b28793af 100644 --- a/pkg/events/eventing.go +++ b/pkg/events/eventing.go @@ -23,15 +23,12 @@ import ( "reflect" "time" - crunchylog "github.com/crunchydata/postgres-operator/internal/logging" "github.com/nsqio/go-nsq" log "github.com/sirupsen/logrus" ) // String returns the string form for a given LogLevel func Publish(e EventInterface) error { - // Add logging configuration - crunchylog.CrunchyLogger(crunchylog.SetParameters()) eventAddr := os.Getenv("EVENT_ADDR") if eventAddr == "" { return errors.New("EVENT_ADDR not set") From 11a51b1a8c8720a5798637f13f128519215ece5c Mon Sep 17 00:00:00 2001 From: Roel Adriaans Date: Fri, 12 Feb 2021 22:32:27 +0100 Subject: [PATCH 187/276] Fix typo in create cluster tutorial Replaces "testuer" with -"testuser". --- docs/content/tutorial/create-cluster.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/tutorial/create-cluster.md b/docs/content/tutorial/create-cluster.md index 423f0a4dbb..3012787e93 100644 --- a/docs/content/tutorial/create-cluster.md +++ b/docs/content/tutorial/create-cluster.md @@ -65,7 +65,7 @@ So what just happened? Let's break down what occurs during the create cluster pr - Creating [persistent volume claims](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) (PVCs) for the PostgreSQL instance and the pgBackRest repository. - Creating [services](https://kubernetes.io/docs/concepts/services-networking/service/) that provide a stable network interface for connecting to the PostgreSQL instance and pgBackRest repository. - Creating [deployments](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) that house each PostgreSQL instance and pgBackRest repository. Each of these is responsible for one Pod. - - The PostgreSQL Pod, when it is started, provisions a PostgreSQL database and performs other bootstrapping functions, such as creating `testuer`. + - The PostgreSQL Pod, when it is started, provisions a PostgreSQL database and performs other bootstrapping functions, such as creating `testuser`. - The pgBackRest Pod, when it is started, initializes a pgBackRest repository. Note that the pgBackRest repository is not yet ready to start taking backups, but will be after the next step! 3. When the PostgreSQL Operator detects that the PostgreSQL and pgBackRest deployments are up and running, it creates a Kubenretes Job to create a pgBackRest stanza. This is necessary as part of intializing the pgBackRest repository to accept backups from our PostgreSQL cluster. From 0339a659f4c277bc7c27a6f8029563bab2a781be Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 16 Feb 2021 23:17:42 -0500 Subject: [PATCH 188/276] 4.6.1 release note updates --- docs/content/releases/4.6.1.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/content/releases/4.6.1.md b/docs/content/releases/4.6.1.md index 2fabbf5dfa..e3fd1587e5 100644 --- a/docs/content/releases/4.6.1.md +++ b/docs/content/releases/4.6.1.md @@ -5,7 +5,7 @@ draft: false weight: 59 --- -Crunchy Data announces the release of the PostgreSQL Operator 4.6.1 on February 15, 2021. +Crunchy Data announces the release of the PostgreSQL Operator 4.6.1 on February 16, 2021. The PostgreSQL Operator is released in conjunction with the [Crunchy Container Suite](https://github.com/CrunchyData/crunchy-containers/). @@ -30,3 +30,4 @@ PostgreSQL Operator is tested against Kubernetes 1.17 - 1.20, OpenShift 3.11, Op - Remove `/crunchyadm` from `unix_socket_directories` configuration during a `pgo upgrade`. Reported by Steven Siahetiong (@ssiahetiong). - Ensure updated paths, i.e. rename to `/opt/crunchy`, are reflected in cluster ConfigMap when running `pgo upgrade`. Reported by Steven Siahetiong (@ssiahetiong). - Ensure value from `--ccp-image-tag` is applied when running `pgo upgrade`. +- Ensure the pgBackRest repository sets ownership settings to the `pgbackrest` user, which, while not noticed under most operating environments, could manifest itself in different UID modes. Reported by Matt Russell (@mjrussell). From 0195dad1c7199987e117322cd5fef194ea41c0a5 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sat, 27 Feb 2021 16:44:49 -0500 Subject: [PATCH 189/276] Revert changes from 05742298 05742298 was designed to workaround an issue with the Docker packaging from Red Hat that introduced a bug which prevented the containers from properly loading. Given we are in a single-user environment that only uses ssh public key authentication for performing pgBackRest operations, we can set "UsePAM" to "no". This allows for one to run the containers with a security context of "allowPrivilegeEscalation" set to "false". Issue: [ch10588] --- .../files/pgo-backrest-repo/sshd_config | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/sshd_config b/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/sshd_config index 3a96f209da..f79f039771 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/sshd_config +++ b/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/sshd_config @@ -80,18 +80,9 @@ ChallengeResponseAuthentication yes #GSSAPIKeyExchange no #GSSAPIEnablek5users no -# Set this to 'yes' to enable PAM authentication, account processing, -# and session processing. If this is enabled, PAM authentication will -# be allowed through the ChallengeResponseAuthentication and -# PasswordAuthentication. Depending on your PAM configuration, -# PAM authentication via ChallengeResponseAuthentication may bypass -# the setting of "PermitRootLogin without-password". -# If you just want the PAM account and session checks to run without -# PAM authentication, then enable this but set PasswordAuthentication -# and ChallengeResponseAuthentication to 'no'. -# WARNING: 'UsePAM no' is not supported in Red Hat Enterprise Linux and may cause several -# problems. -UsePAM yes +# This is set explicitly to *no* as we are only using pubkey authentication and +# because each container is isolated to only an unprivileged user. +UsePAM No #AllowAgentForwarding yes #AllowTcpForwarding yes From 54808bf8ed8e1f9e995369cda215e4699002fb6b Mon Sep 17 00:00:00 2001 From: jmckulk Date: Tue, 23 Feb 2021 12:40:16 -0500 Subject: [PATCH 190/276] Update e2e helper functions to user t.Cleanup When these helper functions were added the t.Cleanup function did not exist. This change removes defer calls in favor of t.Cleanup. --- testing/pgo_cli/suite_helpers_test.go | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/testing/pgo_cli/suite_helpers_test.go b/testing/pgo_cli/suite_helpers_test.go index 57f3a7ca4a..d5c123f556 100644 --- a/testing/pgo_cli/suite_helpers_test.go +++ b/testing/pgo_cli/suite_helpers_test.go @@ -327,12 +327,6 @@ func withCluster(t testing.TB, namespace func() string, during func(func() strin var name string var once sync.Once - defer func() { - if name != "" { - teardownCluster(t, namespace(), name, created) - } - }() - during(func() string { once.Do(func() { generated := names.SimpleNameGenerator.GenerateName("pgo-test-") @@ -343,6 +337,10 @@ func withCluster(t testing.TB, namespace func() string, during func(func() strin created = time.Now() name = generated } + + t.Cleanup(func() { + teardownCluster(t, namespace(), name, created) + }) }) return name }) @@ -364,13 +362,6 @@ func withNamespace(t testing.TB, during func(func() string)) { var namespace *core_v1.Namespace var once sync.Once - defer func() { - if namespace != nil { - err := TestContext.Kubernetes.DeleteNamespace(namespace.Name) - assert.NoErrorf(t, err, "unable to tear down namespace %q", namespace.Name) - } - }() - during(func() string { once.Do(func() { ns, err := TestContext.Kubernetes.GenerateNamespace( @@ -381,6 +372,11 @@ func withNamespace(t testing.TB, during func(func() string)) { _, err = pgo("update", "namespace", namespace.Name).Exec(t) assert.NoErrorf(t, err, "unable to take ownership of namespace %q", namespace.Name) } + + t.Cleanup(func() { + err := TestContext.Kubernetes.DeleteNamespace(namespace.Name) + assert.NoErrorf(t, err, "unable to tear down namespace %q", namespace.Name) + }) }) return namespace.Name From 337a5c59524597d85c88dad0f7510e6343bb5934 Mon Sep 17 00:00:00 2001 From: jmckulk Date: Tue, 23 Feb 2021 12:53:42 -0500 Subject: [PATCH 191/276] Add annotation e2e test This change adds an end-to-end test for the annotation functionality for the operator. Tests are added to create, update, and remove annotations from the cluster deployment. --- testing/kubeapi/deployment.go | 10 + testing/pgo_cli/cluster_annotation_test.go | 247 +++++++++++++++++++++ testing/pgo_cli/suite_helpers_test.go | 10 + 3 files changed, 267 insertions(+) create mode 100644 testing/pgo_cli/cluster_annotation_test.go diff --git a/testing/kubeapi/deployment.go b/testing/kubeapi/deployment.go index 4b864d326a..52ab85e4c9 100644 --- a/testing/kubeapi/deployment.go +++ b/testing/kubeapi/deployment.go @@ -22,3 +22,13 @@ func (k *KubeAPI) ListDeployments(namespace string, labels map[string]string) ([ return list.Items, err } + +// GetDeployment returns deployment by name, if exists. +func (k *KubeAPI) GetDeployment(namespace, name string) *apps_v1.Deployment { + deployment, err := k.Client.AppsV1().Deployments(namespace).Get(name, meta_v1.GetOptions{}) + if deployment == nil && err != nil { + deployment = &apps_v1.Deployment{} + } + + return deployment +} diff --git a/testing/pgo_cli/cluster_annotation_test.go b/testing/pgo_cli/cluster_annotation_test.go new file mode 100644 index 0000000000..e0a2b9286f --- /dev/null +++ b/testing/pgo_cli/cluster_annotation_test.go @@ -0,0 +1,247 @@ +package pgo_cli_test + +/* + Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + 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. +*/ + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestClusterAnnotation(t *testing.T) { + t.Parallel() + + withNamespace(t, func(namespace func() string) { + t.Run("on create", func(t *testing.T) { + t.Parallel() + tests := []struct { + testName string + annotations map[string]string + clusterFlags []string + addFlags []string + removeFlags []string + deployments []string + }{ + { + testName: "create-global", + annotations: map[string]string{ + "global": "here", + "global2": "foo", + }, + clusterFlags: []string{"--pgbouncer"}, + addFlags: []string{"--annotation=global=here", "--annotation=global2=foo"}, + removeFlags: []string{"--annotation=global-", "--annotation=global2-"}, + deployments: []string{"create-global", "create-global-backrest-shared-repo", "create-global-pgbouncer"}, + }, { + testName: "create-postgres", + annotations: map[string]string{ + "postgres": "present", + }, + clusterFlags: []string{}, + addFlags: []string{"--annotation-postgres=postgres=present"}, + removeFlags: []string{"--annotation-postgres=postgres-"}, + deployments: []string{"create-postgres"}, + }, { + testName: "create-pgbackrest", + annotations: map[string]string{ + "pgbackrest": "what", + }, + clusterFlags: []string{}, + addFlags: []string{"--annotation-pgbackrest=pgbackrest=what"}, + removeFlags: []string{"--annotation-pgbackrest=pgbackrest-"}, + deployments: []string{"create-pgbackrest-backrest-shared-repo"}, + }, { + testName: "create-pgbouncer", + annotations: map[string]string{ + "pgbouncer": "aqui", + }, + clusterFlags: []string{"--pgbouncer"}, + addFlags: []string{"--annotation-pgbouncer=pgbouncer=aqui"}, + removeFlags: []string{"--annotation-pgbouncer=pgbouncer-"}, + deployments: []string{"create-pgbouncer-pgbouncer"}, + }, { + testName: "remove-one", + annotations: map[string]string{ + "leave": "me", + }, + clusterFlags: []string{"--pgbouncer"}, + addFlags: []string{"--annotation=remove=me", "--annotation=leave=me"}, + removeFlags: []string{"--annotation=remove-"}, + deployments: []string{"remove-one", "remove-one-backrest-shared-repo", "remove-one-pgbouncer"}, + }, + } + + for _, test := range tests { + test := test // lock test variable in for each run since it changes across parallel loops + t.Run(test.testName, func(t *testing.T) { + t.Parallel() + createCMD := []string{"create", "cluster", test.testName, "-n", namespace()} + createCMD = append(createCMD, test.clusterFlags...) + createCMD = append(createCMD, test.addFlags...) + output, err := pgo(createCMD...).Exec(t) + t.Cleanup(func() { + teardownCluster(t, namespace(), test.testName, time.Now()) + }) + require.NoError(t, err) + require.Contains(t, output, "created cluster:") + + requireClusterReady(t, namespace(), test.testName, (2 * time.Minute)) + if contains(test.clusterFlags, "--pgbouncer") { + requirePgBouncerReady(t, namespace(), test.testName, (2 * time.Minute)) + } + + t.Run("add", func(t *testing.T) { + for _, deploymentName := range test.deployments { + for expectedKey, expectedValue := range test.annotations { + hasAnnotation := func() bool { + actualAnnotations := TestContext.Kubernetes.GetDeployment(namespace(), deploymentName).Spec.Template.ObjectMeta.GetAnnotations() + actualValue := actualAnnotations[expectedKey] + if actualValue == expectedValue { + return true + } + + return false + } + + requireWaitFor(t, hasAnnotation, time.Minute, time.Second, + "timeout waiting for deployment \"%q\" to have annotation \"%s: %s\"", deploymentName, expectedKey, expectedValue) + } + } + }) + + t.Run("remove", func(t *testing.T) { + t.Skip("Bug: annotation in not removed on update") + updateCMD := []string{"update", "cluster", test.testName, "-n", namespace(), "--no-prompt"} + updateCMD = append(updateCMD, test.removeFlags...) + output, err := pgo(updateCMD...).Exec(t) + require.NoError(t, err) + require.Contains(t, output, "updated pgcluster") + + for _, deploymentName := range test.deployments { + for expectedKey, _ := range test.annotations { + notHasAnnotation := func() bool { + actualAnnotations := TestContext.Kubernetes.GetDeployment(namespace(), deploymentName).Spec.Template.ObjectMeta.GetAnnotations() + actualValue := actualAnnotations[expectedKey] + if actualValue != "" { + return false + } + + return true + } + + requireWaitFor(t, notHasAnnotation, time.Minute, time.Second, + "timeout waiting for annotation key \"%s\" to be removed from deployment \"%s\"", expectedKey, deploymentName) + } + } + }) + }) + } + }) + + t.Run("on update", func(t *testing.T) { + t.Parallel() + + tests := []struct { + testName string + annotations map[string]string + clusterFlags []string + addFlags []string + deployments []string + }{ + { + testName: "update-global", + annotations: map[string]string{ + "global": "here", + "global2": "foo", + }, + clusterFlags: []string{"--pgbouncer"}, + addFlags: []string{"--annotation=global=here", "--annotation=global2=foo"}, + deployments: []string{"update-global", "update-global-backrest-shared-repo", "update-global-pgbouncer"}, + }, { + testName: "update-postgres", + annotations: map[string]string{ + "postgres": "present", + }, + clusterFlags: []string{}, + addFlags: []string{"--annotation-postgres=postgres=present"}, + deployments: []string{"update-postgres"}, + }, { + testName: "update-pgbackrest", + annotations: map[string]string{ + "pgbackrest": "what", + }, + clusterFlags: []string{}, + addFlags: []string{"--annotation-pgbackrest=pgbackrest=what"}, + deployments: []string{"update-pgbackrest-backrest-shared-repo"}, + }, { + testName: "update-pgbouncer", + annotations: map[string]string{ + "pgbouncer": "aqui", + }, + clusterFlags: []string{"--pgbouncer"}, + addFlags: []string{"--annotation-pgbouncer=pgbouncer=aqui"}, + deployments: []string{"update-pgbouncer-pgbouncer"}, + }, + } + + for _, test := range tests { + test := test // lock test variable in for each run since it changes across parallel loops + t.Run(test.testName, func(t *testing.T) { + t.Parallel() + createCMD := []string{"create", "cluster", test.testName, "-n", namespace()} + createCMD = append(createCMD, test.clusterFlags...) + output, err := pgo(createCMD...).Exec(t) + t.Cleanup(func() { + teardownCluster(t, namespace(), test.testName, time.Now()) + }) + require.NoError(t, err) + require.Contains(t, output, "created cluster:") + + requireClusterReady(t, namespace(), test.testName, (2 * time.Minute)) + if contains(test.clusterFlags, "--pgbouncer") { + requirePgBouncerReady(t, namespace(), test.testName, (2 * time.Minute)) + } + + updateCMD := []string{"update", "cluster", test.testName, "-n", namespace(), "--no-prompt"} + updateCMD = append(updateCMD, test.addFlags...) + output, err = pgo(updateCMD...).Exec(t) + require.NoError(t, err) + require.Contains(t, output, "updated pgcluster") + + t.Run("add", func(t *testing.T) { + for _, deploymentName := range test.deployments { + for expectedKey, expectedValue := range test.annotations { + hasAnnotation := func() bool { + actualAnnotations := TestContext.Kubernetes.GetDeployment(namespace(), deploymentName).Spec.Template.ObjectMeta.GetAnnotations() + actualValue := actualAnnotations[expectedKey] + if actualValue == expectedValue { + return true + } + + return false + } + + requireWaitFor(t, hasAnnotation, time.Minute, time.Second, + "timeout waiting for deployment \"%q\" to have annotation \"%s: %s\"", deploymentName, expectedKey, expectedValue) + } + } + }) + }) + } + }) + }) +} diff --git a/testing/pgo_cli/suite_helpers_test.go b/testing/pgo_cli/suite_helpers_test.go index d5c123f556..b5f7489c16 100644 --- a/testing/pgo_cli/suite_helpers_test.go +++ b/testing/pgo_cli/suite_helpers_test.go @@ -44,6 +44,16 @@ type Pool struct { func (p *Pool) Close() error { p.Pool.Close(); return p.Proxy.Close() } +// contains will take a string slice and check if it contains a string +func contains(s []string, str string) bool { + for _, v := range s { + if v == str { + return true + } + } + return false +} + // clusterConnection opens a PostgreSQL connection to a database pod. Any error // will cause t to FailNow. func clusterConnection(t testing.TB, namespace, cluster, dsn string) *Pool { From 865d4c6aacf25ccfc641ce2436c6169754b3c521 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 2 Mar 2021 14:58:08 -0500 Subject: [PATCH 192/276] Automatically detect proper setting for "disable FSGroup" In particular, we want to disable explicitly setting the FSGroup when running in OpenShift, i.e. when using the "restricted" SCC (which is the default). This adds detection to see if we are within an OpenShift cluster and, if so, will automatically disable the FSGroup. This also removes the default explicit setting of the disable FSGroup feature from the installer. However, one can still set this option should they so desire. Issue: [ch10571] --- conf/postgres-operator/pgo.yaml | 1 - docs/content/installation/configuration.md | 2 +- docs/content/quickstart/_index.md | 2 +- .../roles/pgo-operator/defaults/main.yml | 1 - .../roles/pgo-operator/templates/pgo.yaml.j2 | 4 +- installers/ansible/values.yaml | 1 - installers/helm/values.yaml | 1 - .../kubectl/postgres-operator-ocp311.yml | 1 - internal/config/pgoconfig.go | 44 +++++++++++++++++-- internal/operator/cluster/pgadmin.go | 2 +- internal/operator/cluster/pgbouncer.go | 2 +- internal/operator/common.go | 2 +- 12 files changed, 49 insertions(+), 14 deletions(-) diff --git a/conf/postgres-operator/pgo.yaml b/conf/postgres-operator/pgo.yaml index 6b790b5435..fcc3064ed7 100644 --- a/conf/postgres-operator/pgo.yaml +++ b/conf/postgres-operator/pgo.yaml @@ -27,7 +27,6 @@ Cluster: DefaultBackrestMemory: DefaultPgBouncerMemory: DefaultExporterMemory: - DisableFSGroup: false PrimaryStorage: default WALStorage: BackupStorage: default diff --git a/docs/content/installation/configuration.md b/docs/content/installation/configuration.md index d984bc34bd..1ce7d34808 100644 --- a/docs/content/installation/configuration.md +++ b/docs/content/installation/configuration.md @@ -46,7 +46,7 @@ Operator. | `delete_operator_namespace` | false | | Set to configure whether or not the PGO operator namespace (defined using variable `pgo_operator_namespace`) is deleted when uninstalling the PGO. | | `delete_watched_namespaces` | false | | Set to configure whether or not the PGO watched namespaces (defined using variable `namespace`) are deleted when uninstalling the PGO. | | `disable_auto_failover` | false | | If set, will disable autofail capabilities by default in any newly created cluster | -| `disable_fsgroup` | false | | Set to `true` for deployments where you do not want to have the default PostgreSQL fsGroup (26) set. The typical usage is in OpenShift environments that have a `restricted` Security Context Constraints. | +| `disable_fsgroup` | | | Set to `true` for deployments where you do not want to have the default PostgreSQL fsGroup (26) set. The typical usage is in OpenShift environments that have a `restricted` Security Context Constraints. If you use the `anyuid` SCC, you would want to set this to `false`. The Postgres Operator will set this value appropriately by default, except for when using the `anyuid` SCC. | | `exporterport` | 9187 | **Required** | Set to configure the default port used to connect to postgres exporter. | | `metrics` | false | **Required** | Set to true enable performance metrics on all newly created clusters. This can be disabled by the client. | | `namespace` | pgo | | Set to a comma delimited string of all the namespaces Operator will manage. | diff --git a/docs/content/quickstart/_index.md b/docs/content/quickstart/_index.md index dd29d467be..d7559ba092 100644 --- a/docs/content/quickstart/_index.md +++ b/docs/content/quickstart/_index.md @@ -326,7 +326,7 @@ primary_storage: "nfsstorage" replica_storage: "nfsstorage" ``` -If you are using either Openshift or CodeReady Containers and you have a `restricted` Security Context Constraint, you will need to set `disable_fsgroup` to `true` in order to deploy the PostgreSQL Operator. +In OpenShift and CodeReady Containers, the PostgreSQL Operator will automatically set `disable_fsgroup` to `true` so that it will deploy PostgreSQL clusters correctly under the `restricted` Security Context Constraint (SCC). Though we recommend using `restricted`, if you are using the `anyuid` SCC, you will need to set `disable_fsgroup` to `false` in order to deploy the PostgreSQL Operator. For a full list of available storage types that can be used with this installation method, please review the [configuration parameters]({{< relref "/installation/configuration.md">}}). diff --git a/installers/ansible/roles/pgo-operator/defaults/main.yml b/installers/ansible/roles/pgo-operator/defaults/main.yml index fb41fa471e..e30ef6eda7 100644 --- a/installers/ansible/roles/pgo-operator/defaults/main.yml +++ b/installers/ansible/roles/pgo-operator/defaults/main.yml @@ -17,7 +17,6 @@ cleanup: "false" common_name: "crunchydata" crunchy_debug: "false" disable_replica_start_fail_reinit: "false" -disable_fsgroup: "false" default_instance_memory: "" default_pgbackrest_memory: "" diff --git a/installers/ansible/roles/pgo-operator/templates/pgo.yaml.j2 b/installers/ansible/roles/pgo-operator/templates/pgo.yaml.j2 index 2c8f272bde..5f70f1d41f 100644 --- a/installers/ansible/roles/pgo-operator/templates/pgo.yaml.j2 +++ b/installers/ansible/roles/pgo-operator/templates/pgo.yaml.j2 @@ -24,11 +24,13 @@ Cluster: PodAntiAffinityPgBackRest: {{ pod_anti_affinity_pgbackrest }} PodAntiAffinityPgBouncer: {{ pod_anti_affinity_pgbouncer }} SyncReplication: {{ sync_replication }} +{% if disable_fsgroup is defined %} + DisableFSGroup: {{ disable_fsgroup }} +{% endif %} DefaultInstanceMemory: {{ default_instance_memory }} DefaultBackrestMemory: {{ default_pgbackrest_memory }} DefaultPgBouncerMemory: {{ default_pgbouncer_memory }} DefaultExporterMemory: {{ default_exporter_memory }} - DisableFSGroup: {{ disable_fsgroup }} PrimaryStorage: {{ primary_storage }} WALStorage: {{ wal_storage }} BackupStorage: {{ backup_storage }} diff --git a/installers/ansible/values.yaml b/installers/ansible/values.yaml index 58007a3d36..5664377961 100644 --- a/installers/ansible/values.yaml +++ b/installers/ansible/values.yaml @@ -33,7 +33,6 @@ default_exporter_memory: "24Mi" delete_operator_namespace: "false" delete_watched_namespaces: "false" disable_auto_failover: "false" -disable_fsgroup: "false" reconcile_rbac: "true" exporterport: "9187" metrics: "false" diff --git a/installers/helm/values.yaml b/installers/helm/values.yaml index 1ff43f22ac..553f29a050 100644 --- a/installers/helm/values.yaml +++ b/installers/helm/values.yaml @@ -53,7 +53,6 @@ default_exporter_memory: "24Mi" delete_operator_namespace: "false" delete_watched_namespaces: "false" disable_auto_failover: "false" -disable_fsgroup: "false" reconcile_rbac: "true" exporterport: "9187" metrics: "false" diff --git a/installers/kubectl/postgres-operator-ocp311.yml b/installers/kubectl/postgres-operator-ocp311.yml index 46cc48774c..a944ed2d93 100644 --- a/installers/kubectl/postgres-operator-ocp311.yml +++ b/installers/kubectl/postgres-operator-ocp311.yml @@ -60,7 +60,6 @@ data: delete_operator_namespace: "false" delete_watched_namespaces: "false" disable_auto_failover: "false" - disable_fsgroup: "false" reconcile_rbac: "true" exporterport: "9187" metrics: "false" diff --git a/internal/config/pgoconfig.go b/internal/config/pgoconfig.go index 97fa7fcb6e..281fdf6600 100644 --- a/internal/config/pgoconfig.go +++ b/internal/config/pgoconfig.go @@ -39,8 +39,9 @@ import ( ) const ( - CustomConfigMapName = "pgo-config" - defaultConfigPath = "/default-pgo-config/" + CustomConfigMapName = "pgo-config" + defaultConfigPath = "/default-pgo-config/" + openShiftAPIGroupSuffix = ".openshift.io" ) var PgoDefaultServiceAccountTemplate *template.Template @@ -218,7 +219,7 @@ type ClusterStruct struct { DefaultBackrestResourceMemory resource.Quantity `json:"DefaultBackrestMemory"` DefaultPgBouncerResourceMemory resource.Quantity `json:"DefaultPgBouncerMemory"` DefaultExporterResourceMemory resource.Quantity `json:"DefaultExporterMemory"` - DisableFSGroup bool + DisableFSGroup *bool } type StorageStruct struct { @@ -255,6 +256,7 @@ type PgoConfig struct { ReplicaStorage string BackrestStorage string Storage map[string]StorageStruct + OpenShift bool } const ( @@ -526,6 +528,9 @@ func (c *PgoConfig) GetConfig(clientset kubernetes.Interface, namespace string) return err } + // determine if this cluster is inside openshift + c.OpenShift = isOpenShift(clientset) + // validate the pgo.yaml config file if err := c.Validate(); err != nil { log.Error(err) @@ -851,3 +856,36 @@ func (c *PgoConfig) CheckEnv() { log.Infof("CheckEnv: using CCP_IMAGE_PREFIX env var: %s", ccpImagePrefix) } } + +// HasDisableFSGroup returns either the value of DisableFSGroup if it is +// explicitly set; otherwise it will determine the value from the environment +func (c *PgoConfig) DisableFSGroup() bool { + if c.Cluster.DisableFSGroup != nil { + log.Debugf("setting disable fsgroup to %t", *c.Cluster.DisableFSGroup) + return *c.Cluster.DisableFSGroup + } + + // if this is OpenShift, disable the FSGroup + log.Debugf("setting disable fsgroup to %t", c.OpenShift) + return c.OpenShift +} + +// isOpenShift returns true if we've detected that we're in an OpenShift cluster +func isOpenShift(clientset kubernetes.Interface) bool { + groups, _, err := clientset.Discovery().ServerGroupsAndResources() + + if err != nil { + log.Errorf("could not get server api groups: %s", err.Error()) + return false + } + + // ff we detect that any API group name ends with "openshift.io", we'll return + // that this is an OpenShift environment + for _, g := range groups { + if strings.HasSuffix(g.Name, openShiftAPIGroupSuffix) { + return true + } + } + + return false +} diff --git a/internal/operator/cluster/pgadmin.go b/internal/operator/cluster/pgadmin.go index ee9ec1ddb9..9f716ea9e1 100644 --- a/internal/operator/cluster/pgadmin.go +++ b/internal/operator/cluster/pgadmin.go @@ -357,7 +357,7 @@ func createPgAdminDeployment(clientset kubernetes.Interface, cluster *crv1.Pgclu CCPImagePrefix: util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, operator.Pgo.Cluster.CCPImagePrefix), CCPImageTag: util.GetValueOrDefault(util.GetStandardImageTag(cluster.Spec.CCPImage, cluster.Spec.CCPImageTag), operator.Pgo.Cluster.CCPImageTag), - DisableFSGroup: operator.Pgo.Cluster.DisableFSGroup, + DisableFSGroup: operator.Pgo.DisableFSGroup(), Port: defPgAdminPort, InitUser: defSetupUsername, InitPass: throwawayPass, diff --git a/internal/operator/cluster/pgbouncer.go b/internal/operator/cluster/pgbouncer.go index 91dc99de2b..e65fdcf143 100644 --- a/internal/operator/cluster/pgbouncer.go +++ b/internal/operator/cluster/pgbouncer.go @@ -567,7 +567,7 @@ func createPgBouncerDeployment(clientset kubernetes.Interface, cluster *crv1.Pgc CCPImagePrefix: util.GetValueOrDefault(cluster.Spec.CCPImagePrefix, operator.Pgo.Cluster.CCPImagePrefix), CCPImageTag: util.GetValueOrDefault(util.GetStandardImageTag(cluster.Spec.CCPImage, cluster.Spec.CCPImageTag), operator.Pgo.Cluster.CCPImageTag), - DisableFSGroup: operator.Pgo.Cluster.DisableFSGroup, + DisableFSGroup: operator.Pgo.DisableFSGroup(), Port: cluster.Spec.Port, PGBouncerConfigMap: util.GeneratePgBouncerConfigMapName(cluster.Name), PGBouncerSecret: util.GeneratePgBouncerSecretName(cluster.Name), diff --git a/internal/operator/common.go b/internal/operator/common.go index 138436b4d2..b9cd27e5ce 100644 --- a/internal/operator/common.go +++ b/internal/operator/common.go @@ -171,7 +171,7 @@ func GetPodSecurityContext(supplementalGroups []int64) string { } // determine if we should use the PostgreSQL FSGroup. - if !Pgo.Cluster.DisableFSGroup { + if !Pgo.DisableFSGroup() { // we store the PostgreSQL FSGroup in this constant as an int64, so it's // just carried over securityContext.FSGroup = &crv1.PGFSGroup From 1caf81f17a33d6b2ddd8f3d4526226f4f5aa9ec8 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Thu, 4 Mar 2021 16:38:32 -0500 Subject: [PATCH 193/276] Fix syntax for restore jobs with node affinity These changes were not carried through when the syntax updates for node affinity were introduced in 9818ac9e. Issue: [ch10696] Issue: #2251 --- .../files/pgo-configs/cluster-bootstrap-job.json | 4 +++- .../pgo-operator/files/pgo-configs/pgrestore-job.json | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json index 42ee6fe2b3..acf683b505 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json @@ -218,7 +218,9 @@ } {{.TablespaceVolumes}}], "affinity": { - {{.NodeSelector}} + {{if .NodeSelector}} + "nodeAffinity": {{.NodeSelector}} + {{ end }} {{if and .NodeSelector .PodAntiAffinity}},{{end}} {{.PodAntiAffinity}} }, diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json index abcd5c2ee3..1feeddfb11 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json @@ -93,8 +93,12 @@ ] } ], - {{.NodeSelector}} - "restartPolicy": "Never" + {{if .NodeSelector}} + "affinity": { + "nodeAffinity": {{.NodeSelector}} + }, + {{ end }} + "restartPolicy": "Never" } } } From 90a8a9c452aab9631a02d62399b5894f6447813f Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sat, 27 Feb 2021 17:38:41 -0500 Subject: [PATCH 194/276] Explicitly set Pod security policies to run as non root The containers are already running as non-root users, so this makes it explicit that this is / must be enforced. Issue: [ch10570] --- deploy/deployment.json | 3 +++ .../files/pgo-configs/pgadmin-template.json | 7 ++++--- .../files/pgo-configs/pgbouncer-template.json | 7 ++++--- .../pgo-configs/pgo.sqlrunner-template.json | 3 +++ .../files/pgo-configs/rmdata-job.json | 3 +++ .../pgo-operator/templates/deployment.json.j2 | 3 +++ .../templates/alertmanager-deployment.json.j2 | 3 ++- .../templates/grafana-deployment.json.j2 | 3 ++- .../templates/prometheus-deployment.json.j2 | 3 ++- installers/olm/postgresoperator.csv.yaml | 2 ++ internal/operator/backrest/backup.go | 2 +- internal/operator/backrest/restore.go | 21 ------------------- internal/operator/common.go | 5 +++++ 13 files changed, 34 insertions(+), 31 deletions(-) diff --git a/deploy/deployment.json b/deploy/deployment.json index e4da5c318b..debe45221e 100644 --- a/deploy/deployment.json +++ b/deploy/deployment.json @@ -24,6 +24,9 @@ }, "spec": { "serviceAccountName": "postgres-operator", + "securityContext": { + "runAsNonRoot": true + }, "containers": [ { "name": "apiserver", diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgadmin-template.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgadmin-template.json index 5ea1d44249..406c192e00 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgadmin-template.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgadmin-template.json @@ -34,11 +34,12 @@ }, "spec": { "serviceAccountName": "pgo-default", - {{ if not .DisableFSGroup }} "securityContext": { - "fsGroup": 2 + {{ if not .DisableFSGroup }} + "fsGroup": 2, + {{ end }} + "runAsNonRoot": true }, - {{ end }} "containers": [{ "name": "pgadminweb", "image": "{{.CCPImagePrefix}}/crunchy-pgadmin4:{{.CCPImageTag}}", diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer-template.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer-template.json index 9e88f4bbdb..003c90714a 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer-template.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer-template.json @@ -39,11 +39,12 @@ }, "spec": { "serviceAccountName": "pgo-default", - {{ if not .DisableFSGroup }} "securityContext": { - "fsGroup": 2 + {{ if not .DisableFSGroup }} + "fsGroup": 2, + {{ end }} + "runAsNonRoot": true }, - {{ end }} "containers": [{ "name": "pgbouncer", "image": "{{.CCPImagePrefix}}/crunchy-pgbouncer:{{.CCPImageTag}}", diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json index 41e1bfc552..1d7479efc5 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json @@ -21,6 +21,9 @@ }, "spec": { "serviceAccountName": "pgo-default", + "securityContext": { + "runAsNonRoot": true + }, {{ if .Tolerations }} "tolerations": {{ .Tolerations }}, {{ end }} diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/rmdata-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/rmdata-job.json index 1b593e181e..b7e58f85b5 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/rmdata-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/rmdata-job.json @@ -21,6 +21,9 @@ }, "spec": { "serviceAccountName": "pgo-target", + "securityContext": { + "runAsNonRoot": true + }, {{ if .Tolerations }} "tolerations": {{ .Tolerations }}, {{ end }} diff --git a/installers/ansible/roles/pgo-operator/templates/deployment.json.j2 b/installers/ansible/roles/pgo-operator/templates/deployment.json.j2 index b94ab4fc42..004879186d 100644 --- a/installers/ansible/roles/pgo-operator/templates/deployment.json.j2 +++ b/installers/ansible/roles/pgo-operator/templates/deployment.json.j2 @@ -24,6 +24,9 @@ }, "spec": { "serviceAccountName": "postgres-operator", + "securityContext": { + "runAsNonRoot": true + }, "containers": [ { "name": "apiserver", diff --git a/installers/metrics/ansible/roles/pgo-metrics/templates/alertmanager-deployment.json.j2 b/installers/metrics/ansible/roles/pgo-metrics/templates/alertmanager-deployment.json.j2 index bcae32cc41..6f4ef836d3 100644 --- a/installers/metrics/ansible/roles/pgo-metrics/templates/alertmanager-deployment.json.j2 +++ b/installers/metrics/ansible/roles/pgo-metrics/templates/alertmanager-deployment.json.j2 @@ -30,7 +30,8 @@ {% if not (disable_fsgroup | default(false) | bool) %} {% if (alertmanager_supplemental_groups | default('')) != '' %},{% endif -%} "fsGroup": 26, - "runAsUser": 2 + "runAsUser": 2, + "runAsNonRoot": true {% endif %} }, "serviceAccountName": "alertmanager", diff --git a/installers/metrics/ansible/roles/pgo-metrics/templates/grafana-deployment.json.j2 b/installers/metrics/ansible/roles/pgo-metrics/templates/grafana-deployment.json.j2 index f3815541d6..7759430969 100644 --- a/installers/metrics/ansible/roles/pgo-metrics/templates/grafana-deployment.json.j2 +++ b/installers/metrics/ansible/roles/pgo-metrics/templates/grafana-deployment.json.j2 @@ -30,7 +30,8 @@ {% if not (disable_fsgroup | default(false) | bool) %} {% if (grafana_supplemental_groups | default('')) != '' %},{% endif -%} "fsGroup": 26, - "runAsUser": 2 + "runAsUser": 2, + "runAsNonRoot": true {% endif %} }, "serviceAccountName": "grafana", diff --git a/installers/metrics/ansible/roles/pgo-metrics/templates/prometheus-deployment.json.j2 b/installers/metrics/ansible/roles/pgo-metrics/templates/prometheus-deployment.json.j2 index 64980cb2b5..07a8b2e219 100644 --- a/installers/metrics/ansible/roles/pgo-metrics/templates/prometheus-deployment.json.j2 +++ b/installers/metrics/ansible/roles/pgo-metrics/templates/prometheus-deployment.json.j2 @@ -30,7 +30,8 @@ {% if not (disable_fsgroup | default(false) | bool) %} {% if (prometheus_supplemental_groups | default('')) != '' %},{% endif -%} "fsGroup": 26, - "runAsUser": 2 + "runAsUser": 2, + "runAsNonRoot": true, {% endif %} }, "serviceAccountName": "prometheus-sa", diff --git a/installers/olm/postgresoperator.csv.yaml b/installers/olm/postgresoperator.csv.yaml index a6ae30d051..e4a8310fc5 100644 --- a/installers/olm/postgresoperator.csv.yaml +++ b/installers/olm/postgresoperator.csv.yaml @@ -200,6 +200,8 @@ spec: vendor: crunchydata spec: serviceAccountName: postgres-operator + securityContext: + runAsNonRoot: true containers: - name: apiserver image: '${PGO_IMAGE_PREFIX}/pgo-apiserver:${PGO_IMAGE_TAG}' diff --git a/internal/operator/backrest/backup.go b/internal/operator/backrest/backup.go index 1eada2d930..cd4f31ed05 100644 --- a/internal/operator/backrest/backup.go +++ b/internal/operator/backrest/backup.go @@ -104,7 +104,7 @@ func Backrest(namespace string, clientset kubeapi.Interface, task *crv1.Pgtask) JobName: task.Spec.Parameters[config.LABEL_JOB_NAME], ClusterName: task.Spec.Parameters[config.LABEL_PG_CLUSTER], PodName: task.Spec.Parameters[config.LABEL_POD_NAME], - SecurityContext: "{}", + SecurityContext: `{"runAsNonRoot": true}`, Command: cmd, CommandOpts: task.Spec.Parameters[config.LABEL_BACKREST_OPTS], PITRTarget: "", diff --git a/internal/operator/backrest/restore.go b/internal/operator/backrest/restore.go index 92fdd9fd04..da631f8758 100644 --- a/internal/operator/backrest/restore.go +++ b/internal/operator/backrest/restore.go @@ -49,27 +49,6 @@ const ( // for pgBackRest using the '--target' option var restoreTargetRegex = regexp.MustCompile("--target(=| +)") -type BackrestRestoreJobTemplateFields struct { - JobName string - ClusterName string - WorkflowID string - ToClusterPVCName string - SecurityContext string - CCPImagePrefix string - CCPImageTag string - CommandOpts string - PITRTarget string - PgbackrestStanza string - PgbackrestDBPath string - PgbackrestRepo1Path string - PgbackrestRepo1Host string - PgbackrestS3EnvVars string - NodeSelector string - Tablespaces string - TablespaceVolumes string - TablespaceVolumeMounts string -} - // UpdatePGClusterSpecForRestore updates the spec for pgcluster resource provided as need to // perform a restore func UpdatePGClusterSpecForRestore(clientset kubeapi.Interface, cluster *crv1.Pgcluster, diff --git a/internal/operator/common.go b/internal/operator/common.go index b9cd27e5ce..e78f447556 100644 --- a/internal/operator/common.go +++ b/internal/operator/common.go @@ -72,6 +72,9 @@ var ContainerImageOverrides = map[string]string{} // for detailed explanations of each mode available. var namespaceOperatingMode ns.NamespaceOperatingMode +// runAsNonRoot forces the Pod to run as a non-root Pod +var runAsNonRoot = true + type containerResourcesTemplateFields struct { // LimitsMemory and LimitsCPU detemrine the memory/CPU limits LimitsMemory, LimitsCPU string @@ -166,6 +169,8 @@ func Initialize(clientset kubernetes.Interface) { func GetPodSecurityContext(supplementalGroups []int64) string { // set up the security context struct securityContext := v1.PodSecurityContext{ + // we don't want to run the pods as root, so explicitly disallow this + RunAsNonRoot: &runAsNonRoot, // add any supplemental groups that the user passed in SupplementalGroups: supplementalGroups, } From 55c2d9b98cca184cf910c85181d5d2e0555bdffa Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sat, 27 Feb 2021 19:39:35 -0500 Subject: [PATCH 195/276] Explicitly disallow allowPrivilegeEscalation on containers There is no need for the containers to escalate privileges, ergo we can disallow them from attempting to do so. Issue: [ch10570] --- deploy/deployment.json | 12 ++++++++++++ .../pgo-operator/files/pgo-configs/backrest-job.json | 3 +++ .../files/pgo-configs/cluster-bootstrap-job.json | 3 +++ .../files/pgo-configs/cluster-deployment.json | 3 +++ .../pgo-operator/files/pgo-configs/exporter.json | 3 +++ .../files/pgo-configs/pgadmin-template.json | 3 +++ .../pgo-operator/files/pgo-configs/pgbadger.json | 3 +++ .../files/pgo-configs/pgbouncer-template.json | 3 +++ .../pgo-operator/files/pgo-configs/pgdump-job.json | 3 +++ .../pgo-configs/pgo-backrest-repo-template.json | 3 +++ .../files/pgo-configs/pgo.sqlrunner-template.json | 3 +++ .../files/pgo-configs/pgrestore-job.json | 3 +++ .../pgo-operator/files/pgo-configs/rmdata-job.json | 3 +++ .../roles/pgo-operator/templates/deployment.json.j2 | 12 ++++++++++++ installers/olm/postgresoperator.csv.yaml | 8 ++++++++ 15 files changed, 68 insertions(+) diff --git a/deploy/deployment.json b/deploy/deployment.json index debe45221e..8f032b425c 100644 --- a/deploy/deployment.json +++ b/deploy/deployment.json @@ -32,6 +32,9 @@ "name": "apiserver", "image": "$PGO_IMAGE_PREFIX/pgo-apiserver:$PGO_IMAGE_TAG", "imagePullPolicy": "IfNotPresent", + "securityContext": { + "allowPrivilegeEscalation": false + }, "ports": [ { "containerPort": $PGO_APISERVER_PORT } ], @@ -112,6 +115,9 @@ "name": "operator", "image": "$PGO_IMAGE_PREFIX/postgres-operator:$PGO_IMAGE_TAG", "imagePullPolicy": "IfNotPresent", + "securityContext": { + "allowPrivilegeEscalation": false + }, "readinessProbe": { "exec": { "command": [ @@ -164,6 +170,9 @@ }, { "name": "scheduler", "image": "$PGO_IMAGE_PREFIX/pgo-scheduler:$PGO_IMAGE_TAG", + "securityContext": { + "allowPrivilegeEscalation": false + }, "livenessProbe": { "exec": { "command": [ @@ -212,6 +221,9 @@ { "name": "event", "image": "$PGO_IMAGE_PREFIX/pgo-event:$PGO_IMAGE_TAG", + "securityContext": { + "allowPrivilegeEscalation": false + }, "livenessProbe": { "httpGet": { "path": "/ping", diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json index b2512650f5..1025dd9b7d 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json @@ -35,6 +35,9 @@ "containers": [{ "name": "backrest", "image": "{{.CCPImagePrefix}}/crunchy-pgbackrest:{{.CCPImageTag}}", + "securityContext": { + "allowPrivilegeEscalation": false + }, "volumeMounts": [ {{.PgbackrestRestoreVolumeMounts}} ], diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json index acf683b505..6143227cc0 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json @@ -29,6 +29,9 @@ "containers": [{ "name": "database", "image": "{{.CCPImagePrefix}}/{{.CCPImage}}:{{.CCPImageTag}}", + "securityContext": { + "allowPrivilegeEscalation": false + }, {{.ContainerResources}} "env": [{ "name": "PGHA_PG_PORT", diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json index e9f0367b8e..7d86118653 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json @@ -42,6 +42,9 @@ { "name": "database", "image": "{{.CCPImagePrefix}}/{{.CCPImage}}:{{.CCPImageTag}}", + "securityContext": { + "allowPrivilegeEscalation": false + }, "readinessProbe": { "exec": { "command": [ diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/exporter.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/exporter.json index 3a3d5edddd..3f522d574a 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/exporter.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/exporter.json @@ -1,6 +1,9 @@ { "name": "exporter", "image": "{{.PGOImagePrefix}}/crunchy-postgres-exporter:{{.PGOImageTag}}", + "securityContext": { + "allowPrivilegeEscalation": false + }, "ports": [{ "containerPort": {{.ExporterPort}}, "protocol": "TCP" diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgadmin-template.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgadmin-template.json index 406c192e00..b97cdfb944 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgadmin-template.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgadmin-template.json @@ -43,6 +43,9 @@ "containers": [{ "name": "pgadminweb", "image": "{{.CCPImagePrefix}}/crunchy-pgadmin4:{{.CCPImageTag}}", + "securityContext": { + "allowPrivilegeEscalation": false + }, "ports": [{ "containerPort": {{.Port}}, "protocol": "TCP" diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbadger.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbadger.json index ce03aaabb7..61277bc887 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbadger.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbadger.json @@ -1,6 +1,9 @@ { "name": "pgbadger", "image": "{{.CCPImagePrefix}}/crunchy-pgbadger:{{.CCPImageTag}}", + "securityContext": { + "allowPrivilegeEscalation": false + }, "ports": [ { "containerPort": {{.PGBadgerPort}}, "protocol": "TCP" diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer-template.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer-template.json index 003c90714a..f3c85f5f3d 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer-template.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer-template.json @@ -48,6 +48,9 @@ "containers": [{ "name": "pgbouncer", "image": "{{.CCPImagePrefix}}/crunchy-pgbouncer:{{.CCPImageTag}}", + "securityContext": { + "allowPrivilegeEscalation": false + }, "ports": [{ "containerPort": {{.Port}}, "protocol": "TCP" diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgdump-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgdump-job.json index 9c44c0ce06..cc7b60ad55 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgdump-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgdump-job.json @@ -37,6 +37,9 @@ "containers": [{ "name": "pgdump", "image": "{{.CCPImagePrefix}}/crunchy-postgres-ha:{{.CCPImageTag}}", + "securityContext": { + "allowPrivilegeEscalation": false + }, "command": ["/opt/crunchy/bin/uid_postgres.sh"], "args": ["/opt/crunchy/bin/start.sh"], "volumeMounts": [ diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json index 32a67d9a99..58d7d86823 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json @@ -52,6 +52,9 @@ "containers": [{ "name": "database", "image": "{{.CCPImagePrefix}}/crunchy-pgbackrest-repo:{{.CCPImageTag}}", + "securityContext": { + "allowPrivilegeEscalation": false + }, "ports": [{ "containerPort": {{.SshdPort}}, "protocol": "TCP" diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json index 1d7479efc5..2b0854223a 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json @@ -31,6 +31,9 @@ { "name": "sqlrunner", "image": "{{.CCPImagePrefix}}/crunchy-postgres-ha:{{.CCPImageTag}}", + "securityContext": { + "allowPrivilegeEscalation": false + }, "command": ["/opt/crunchy/bin/uid_postgres.sh"], "args": ["/opt/crunchy/bin/start.sh"], "env": [ diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json index 1feeddfb11..96520e4394 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json @@ -38,6 +38,9 @@ { "name": "pgrestore", "image": "{{.CCPImagePrefix}}/crunchy-postgres-ha:{{.CCPImageTag}}", + "securityContext": { + "allowPrivilegeEscalation": false + }, "command": ["/opt/crunchy/bin/uid_postgres.sh"], "args": ["/opt/crunchy/bin/start.sh"], "volumeMounts": [ diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/rmdata-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/rmdata-job.json index b7e58f85b5..25e033caec 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/rmdata-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/rmdata-job.json @@ -30,6 +30,9 @@ "containers": [{ "name": "rmdata", "image": "{{.PGOImagePrefix}}/pgo-rmdata:{{.PGOImageTag}}", + "securityContext": { + "allowPrivilegeEscalation": false + }, "env": [{ "name": "PG_CLUSTER", "value": "{{.ClusterName}}" diff --git a/installers/ansible/roles/pgo-operator/templates/deployment.json.j2 b/installers/ansible/roles/pgo-operator/templates/deployment.json.j2 index 004879186d..40361eb272 100644 --- a/installers/ansible/roles/pgo-operator/templates/deployment.json.j2 +++ b/installers/ansible/roles/pgo-operator/templates/deployment.json.j2 @@ -34,6 +34,9 @@ {%- else %}{{ pgo_image_prefix }}/pgo-apiserver:{{ pgo_image_tag }} {%- endif %}", "imagePullPolicy": "IfNotPresent", + "securityContext": { + "allowPrivilegeEscalation": false + }, "ports": [ { "containerPort": {{ pgo_apiserver_port }} } ], @@ -116,6 +119,9 @@ {%- else %}{{ pgo_image_prefix }}/postgres-operator:{{ pgo_image_tag }} {%- endif %}", "imagePullPolicy": "IfNotPresent", + "securityContext": { + "allowPrivilegeEscalation": false + }, "readinessProbe": { "exec": { "command": [ @@ -170,6 +176,9 @@ "image": "{% if pgo_scheduler_image | default('') != '' %}{{ pgo_scheduler_image }} {%- else %}{{ pgo_image_prefix }}/pgo-scheduler:{{ pgo_image_tag }} {%- endif %}", + "securityContext": { + "allowPrivilegeEscalation": false + }, "livenessProbe": { "exec": { "command": [ @@ -219,6 +228,9 @@ "image": "{% if pgo_event_image | default('') != '' %}{{ pgo_event_image }} {%- else %}{{ pgo_image_prefix }}/pgo-event:{{ pgo_image_tag }} {%- endif %}", + "securityContext": { + "allowPrivilegeEscalation": false + }, "livenessProbe": { "httpGet": { "path": "/ping", diff --git a/installers/olm/postgresoperator.csv.yaml b/installers/olm/postgresoperator.csv.yaml index e4a8310fc5..02650a3f5b 100644 --- a/installers/olm/postgresoperator.csv.yaml +++ b/installers/olm/postgresoperator.csv.yaml @@ -206,6 +206,8 @@ spec: - name: apiserver image: '${PGO_IMAGE_PREFIX}/pgo-apiserver:${PGO_IMAGE_TAG}' imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false ports: - containerPort: 8443 readinessProbe: @@ -234,6 +236,8 @@ spec: - name: operator image: '${PGO_IMAGE_PREFIX}/postgres-operator:${PGO_IMAGE_TAG}' imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false env: - { name: NAMESPACE, valueFrom: { fieldRef: { fieldPath: "metadata.annotations['olm.targetNamespaces']" } } } - { name: PGO_INSTALLATION_NAME, valueFrom: { fieldRef: { fieldPath: "metadata.namespace" } } } @@ -245,6 +249,8 @@ spec: - name: scheduler image: '${PGO_IMAGE_PREFIX}/pgo-scheduler:${PGO_IMAGE_TAG}' imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false livenessProbe: exec: command: [ @@ -267,6 +273,8 @@ spec: - name: event image: '${PGO_IMAGE_PREFIX}/pgo-event:${PGO_IMAGE_TAG}' imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false livenessProbe: httpGet: path: /ping From 000f58c1e755dfc525141afc571f6a08a4eeb062 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sat, 27 Feb 2021 17:41:39 -0500 Subject: [PATCH 196/276] Fully support readOnlyRootFilesystem for each deployed container This adds support for the readOnlyRootFilesystem security context attribute, which locks down the container filesystem for writing, except for specified mount points. The Operator containers mostly supported this, but required adding some ephemeral storage mounts for interacting with temporary storage, e.g. /tmp Reviewed-by: Joseph Cho Issue: [ch7571] --- deploy/deployment.json | 43 +++++++++++++++---- .../files/pgo-configs/backrest-job.json | 14 +++++- .../pgo-configs/cluster-bootstrap-job.json | 25 ++++++++++- .../files/pgo-configs/cluster-deployment.json | 25 ++++++++++- .../files/pgo-configs/exporter.json | 9 +++- .../files/pgo-configs/pgadmin-template.json | 34 +++++++++++++-- .../files/pgo-configs/pgbadger.json | 7 ++- .../files/pgo-configs/pgbouncer-template.json | 14 +++++- .../files/pgo-configs/pgdump-job.json | 17 ++++++-- .../pgo-backrest-repo-template.json | 39 ++++++++++++++++- .../pgo-configs/pgo.sqlrunner-template.json | 3 +- .../files/pgo-configs/pgrestore-job.json | 14 +++++- .../files/pgo-configs/rmdata-job.json | 3 +- .../pgo-operator/templates/deployment.json.j2 | 43 +++++++++++++++---- installers/olm/postgresoperator.csv.yaml | 18 ++++++++ 15 files changed, 274 insertions(+), 34 deletions(-) diff --git a/deploy/deployment.json b/deploy/deployment.json index 8f032b425c..699536c75b 100644 --- a/deploy/deployment.json +++ b/deploy/deployment.json @@ -33,7 +33,8 @@ "image": "$PGO_IMAGE_PREFIX/pgo-apiserver:$PGO_IMAGE_TAG", "imagePullPolicy": "IfNotPresent", "securityContext": { - "allowPrivilegeEscalation": false + "allowPrivilegeEscalation": false, + "readOnlyRootFilesystem": true }, "ports": [ { "containerPort": $PGO_APISERVER_PORT } @@ -110,13 +111,19 @@ "value": "localhost:4150" } ], - "volumeMounts": [] + "volumeMounts": [ + { + "mountPath": "/tmp", + "name": "tmp" + } + ] }, { "name": "operator", "image": "$PGO_IMAGE_PREFIX/postgres-operator:$PGO_IMAGE_TAG", "imagePullPolicy": "IfNotPresent", "securityContext": { - "allowPrivilegeEscalation": false + "allowPrivilegeEscalation": false, + "readOnlyRootFilesystem": true }, "readinessProbe": { "exec": { @@ -171,7 +178,8 @@ "name": "scheduler", "image": "$PGO_IMAGE_PREFIX/pgo-scheduler:$PGO_IMAGE_TAG", "securityContext": { - "allowPrivilegeEscalation": false + "allowPrivilegeEscalation": false, + "readOnlyRootFilesystem": true }, "livenessProbe": { "exec": { @@ -215,14 +223,20 @@ "value": "localhost:4150" } ], - "volumeMounts": [], + "volumeMounts": [ + { + "mountPath": "/tmp", + "name": "tmp" + } + ], "imagePullPolicy": "IfNotPresent" }, { "name": "event", "image": "$PGO_IMAGE_PREFIX/pgo-event:$PGO_IMAGE_TAG", "securityContext": { - "allowPrivilegeEscalation": false + "allowPrivilegeEscalation": false, + "readOnlyRootFilesystem": true }, "livenessProbe": { "httpGet": { @@ -238,11 +252,24 @@ "value": "3600" } ], - "volumeMounts": [], + "volumeMounts": [ + { + "mountPath": "/tmp", + "name": "tmp" + } + ], "imagePullPolicy": "IfNotPresent" } ], - "volumes": [] + "volumes": [ + { + "name": "tmp", + "emptyDir": { + "medium": "Memory", + "sizeLimit": "16Mi" + } + } + ] } } } diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json index 1025dd9b7d..9a52b1cf78 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json @@ -25,6 +25,13 @@ }, "spec": { "volumes": [ + { + "name": "tmp", + "emptyDir": { + "medium": "Memory", + "sizeLimit": "16Mi" + } + }{{ if .PgbackrestRestoreVolumes }},{{ end }} {{.PgbackrestRestoreVolumes}} ], "securityContext": {{.SecurityContext}}, @@ -36,9 +43,14 @@ "name": "backrest", "image": "{{.CCPImagePrefix}}/crunchy-pgbackrest:{{.CCPImageTag}}", "securityContext": { - "allowPrivilegeEscalation": false + "allowPrivilegeEscalation": false, + "readOnlyRootFilesystem": true }, "volumeMounts": [ + { + "mountPath": "/tmp", + "name": "tmp" + }{{ if .PgbackrestRestoreVolumeMounts }},{{ end }} {{.PgbackrestRestoreVolumeMounts}} ], "env": [{ diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json index 6143227cc0..2b898af652 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json @@ -30,7 +30,8 @@ "name": "database", "image": "{{.CCPImagePrefix}}/{{.CCPImage}}:{{.CCPImageTag}}", "securityContext": { - "allowPrivilegeEscalation": false + "allowPrivilegeEscalation": false, + "readOnlyRootFilesystem": true }, {{.ContainerResources}} "env": [{ @@ -137,6 +138,14 @@ }, { "mountPath": "/dev/shm", "name": "dshm" + }, + { + "mountPath": "/tmp", + "name": "tmp" + }, + { + "mountPath": "/var/lib/pgsql/.ssh", + "name": "pgbackrest-ssh" }, { "mountPath": "/etc/pgbackrest/conf.d", "name": "pgbackrest-config" @@ -169,6 +178,13 @@ "secretName": "{{.RestoreFrom}}-backrest-repo-config" } }, + { + "name": "pgbackrest-ssh", + "emptyDir": { + "medium": "Memory", + "sizeLimit": "128Ki" + } + }, {{if .TLSEnabled}} { "name": "tls-server", @@ -218,6 +234,13 @@ } ] } + }, + { + "name": "tmp", + "emptyDir": { + "medium": "Memory", + "sizeLimit": "16Mi" + } } {{.TablespaceVolumes}}], "affinity": { diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json index 7d86118653..a46cf7a472 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json @@ -43,7 +43,8 @@ "name": "database", "image": "{{.CCPImagePrefix}}/{{.CCPImage}}:{{.CCPImageTag}}", "securityContext": { - "allowPrivilegeEscalation": false + "allowPrivilegeEscalation": false, + "readOnlyRootFilesystem": true }, "readinessProbe": { "exec": { @@ -191,6 +192,14 @@ { "mountPath": "/etc/podinfo", "name": "podinfo" + }, + { + "mountPath": "/tmp", + "name": "tmp" + }, + { + "mountPath": "/var/lib/pgsql/.ssh", + "name": "pgbackrest-ssh" } {{.TablespaceVolumeMounts}} ], @@ -294,6 +303,20 @@ "medium": "Memory" } }, + { + "name": "tmp", + "emptyDir": { + "medium": "Memory", + "sizeLimit": "16Mi" + } + }, + { + "name": "pgbackrest-ssh", + "emptyDir": { + "medium": "Memory", + "sizeLimit": "128Ki" + } + }, { "name": "pgbackrest-config", "projected": { "sources": [] } diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/exporter.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/exporter.json index 3f522d574a..846f3982cb 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/exporter.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/exporter.json @@ -2,7 +2,8 @@ "name": "exporter", "image": "{{.PGOImagePrefix}}/crunchy-postgres-exporter:{{.PGOImageTag}}", "securityContext": { - "allowPrivilegeEscalation": false + "allowPrivilegeEscalation": false, + "readOnlyRootFilesystem": true }, "ports": [{ "containerPort": {{.ExporterPort}}, @@ -52,5 +53,11 @@ } } } + ], + "volumeMounts": [ + { + "mountPath": "/tmp", + "name": "tmp" + } ] } diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgadmin-template.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgadmin-template.json index b97cdfb944..38cd7c32c8 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgadmin-template.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgadmin-template.json @@ -44,7 +44,8 @@ "name": "pgadminweb", "image": "{{.CCPImagePrefix}}/crunchy-pgadmin4:{{.CCPImageTag}}", "securityContext": { - "allowPrivilegeEscalation": false + "allowPrivilegeEscalation": false, + "readOnlyRootFilesystem": true }, "ports": [{ "containerPort": {{.Port}}, @@ -58,12 +59,37 @@ "value": "{{.InitPass}}" }], "volumeMounts": [{ + "name": "tmp", + "mountPath": "/tmp" + }, + { + "name": "pgadmin-log", + "mountPath": "/var/log/pgadmin" + }, + { + "name": "tmp", + "mountPath": "/etc/httpd/run" + }, + { "name": "pgadmin-datadir", - "mountPath": "/var/lib/pgadmin", - "readOnly": false - }] + "mountPath": "/var/lib/pgadmin" + }] }], "volumes": [{ + "name": "tmp", + "emptyDir": { + "medium": "Memory", + "sizeLimit": "16Mi" + } + }, + { + "name": "pgadmin-log", + "emptyDir": { + "medium": "Memory", + "sizeLimit": "16Mi" + } + }, + { "name": "pgadmin-datadir", "persistentVolumeClaim": { "claimName": "{{.PVCName}}" diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbadger.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbadger.json index 61277bc887..9db32a07c6 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbadger.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbadger.json @@ -2,7 +2,8 @@ "name": "pgbadger", "image": "{{.CCPImagePrefix}}/crunchy-pgbadger:{{.CCPImageTag}}", "securityContext": { - "allowPrivilegeEscalation": false + "allowPrivilegeEscalation": false, + "readOnlyRootFilesystem": true }, "ports": [ { "containerPort": {{.PGBadgerPort}}, @@ -30,6 +31,10 @@ } }, "volumeMounts": [ + { + "mountPath": "/tmp", + "name": "tmp" + }, { "mountPath": "/pgdata", "name": "pgdata", diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer-template.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer-template.json index f3c85f5f3d..43f2f31487 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer-template.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer-template.json @@ -49,7 +49,8 @@ "name": "pgbouncer", "image": "{{.CCPImagePrefix}}/crunchy-pgbouncer:{{.CCPImageTag}}", "securityContext": { - "allowPrivilegeEscalation": false + "allowPrivilegeEscalation": false, + "readOnlyRootFilesystem": true }, "ports": [{ "containerPort": {{.Port}}, @@ -69,6 +70,10 @@ "value": "{{.PrimaryServiceName}}" }], "volumeMounts": [ + { + "mountPath": "/tmp", + "name": "tmp" + }, {{if .TLSEnabled}} { "mountPath": "/pgconf/tls/pgbouncer", @@ -83,6 +88,13 @@ ] }], "volumes": [ + { + "name": "tmp", + "emptyDir": { + "medium": "Memory", + "sizeLimit": "1Mi" + } + }, {{if .TLSEnabled}} { "name": "tls-pgbouncer", diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgdump-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgdump-job.json index cc7b60ad55..cebb3ddb8a 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgdump-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgdump-job.json @@ -22,6 +22,13 @@ }, "spec": { "volumes": [ + { + "name": "tmp", + "emptyDir": { + "medium": "Memory", + "sizeLimit": "1Mi" + } + }, { "name": "pgdata", "persistentVolumeClaim": { @@ -38,15 +45,19 @@ "name": "pgdump", "image": "{{.CCPImagePrefix}}/crunchy-postgres-ha:{{.CCPImageTag}}", "securityContext": { - "allowPrivilegeEscalation": false + "allowPrivilegeEscalation": false, + "readOnlyRootFilesystem": true }, "command": ["/opt/crunchy/bin/uid_postgres.sh"], "args": ["/opt/crunchy/bin/start.sh"], "volumeMounts": [ + { + "mountPath": "/tmp", + "name": "tmp" + }, { "mountPath": "/pgdata", - "name": "pgdata", - "readOnly": false + "name": "pgdata" } ], "env": [ diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json index 58d7d86823..187ea43bc6 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json @@ -53,7 +53,8 @@ "name": "database", "image": "{{.CCPImagePrefix}}/crunchy-pgbackrest-repo:{{.CCPImageTag}}", "securityContext": { - "allowPrivilegeEscalation": false + "allowPrivilegeEscalation": false, + "readOnlyRootFilesystem": true }, "ports": [{ "containerPort": {{.SshdPort}}, @@ -103,7 +104,20 @@ "name": "backrestrepo", "mountPath": "/backrestrepo", "readOnly": false - }] + }, + { + "name": "tmp", + "mountPath": "/tmp" + }, + { + "name": "pgbackrest-home", + "mountPath": "/home/pgbackrest" + }, + { + "name": "pgbackrest-conf", + "mountPath": "/etc/pgbackrest" + } + ] }], "volumes": [{ "name": "sshd", @@ -115,6 +129,27 @@ "persistentVolumeClaim": { "claimName": "{{.BackrestRepoClaimName}}" } + }, + { + "name": "tmp", + "emptyDir": { + "medium": "Memory", + "sizeLimit": "64Mi" + } + }, + { + "name": "pgbackrest-home", + "emptyDir": { + "medium": "Memory", + "sizeLimit": "128Ki" + } + }, + { + "name": "pgbackrest-conf", + "emptyDir": { + "medium": "Memory", + "sizeLimit": "128Ki" + } }], "affinity": { {{.PodAntiAffinity}} diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json index 2b0854223a..98ff092e53 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json @@ -32,7 +32,8 @@ "name": "sqlrunner", "image": "{{.CCPImagePrefix}}/crunchy-postgres-ha:{{.CCPImageTag}}", "securityContext": { - "allowPrivilegeEscalation": false + "allowPrivilegeEscalation": false, + "readOnlyRootFilesystem": true }, "command": ["/opt/crunchy/bin/uid_postgres.sh"], "args": ["/opt/crunchy/bin/start.sh"], diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json index 96520e4394..cc52280075 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json @@ -22,6 +22,13 @@ }, "spec": { "volumes": [ + { + "name": "tmp", + "emptyDir": { + "medium": "Memory", + "sizeLimit": "1Mi" + } + }, { "name": "pgdata", "persistentVolumeClaim": { @@ -39,11 +46,16 @@ "name": "pgrestore", "image": "{{.CCPImagePrefix}}/crunchy-postgres-ha:{{.CCPImageTag}}", "securityContext": { - "allowPrivilegeEscalation": false + "allowPrivilegeEscalation": false, + "readOnlyRootFilesystem": true }, "command": ["/opt/crunchy/bin/uid_postgres.sh"], "args": ["/opt/crunchy/bin/start.sh"], "volumeMounts": [ + { + "mountPath": "/tmp", + "name": "tmp" + }, { "mountPath": "/pgdata", "name": "pgdata", diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/rmdata-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/rmdata-job.json index 25e033caec..55e681e2a9 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/rmdata-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/rmdata-job.json @@ -31,7 +31,8 @@ "name": "rmdata", "image": "{{.PGOImagePrefix}}/pgo-rmdata:{{.PGOImageTag}}", "securityContext": { - "allowPrivilegeEscalation": false + "allowPrivilegeEscalation": false, + "readOnlyRootFilesystem": true }, "env": [{ "name": "PG_CLUSTER", diff --git a/installers/ansible/roles/pgo-operator/templates/deployment.json.j2 b/installers/ansible/roles/pgo-operator/templates/deployment.json.j2 index 40361eb272..05241dd9b9 100644 --- a/installers/ansible/roles/pgo-operator/templates/deployment.json.j2 +++ b/installers/ansible/roles/pgo-operator/templates/deployment.json.j2 @@ -35,7 +35,8 @@ {%- endif %}", "imagePullPolicy": "IfNotPresent", "securityContext": { - "allowPrivilegeEscalation": false + "allowPrivilegeEscalation": false, + "readOnlyRootFilesystem": true }, "ports": [ { "containerPort": {{ pgo_apiserver_port }} } @@ -112,7 +113,12 @@ "value": "localhost:4150" } ], - "volumeMounts": [] + "volumeMounts": [ + { + "mountPath": "/tmp", + "name": "tmp" + } + ] }, { "name": "operator", "image": "{% if pgo_image | default('') != '' %}{{ pgo_image }} @@ -120,7 +126,8 @@ {%- endif %}", "imagePullPolicy": "IfNotPresent", "securityContext": { - "allowPrivilegeEscalation": false + "allowPrivilegeEscalation": false, + "readOnlyRootFilesystem": true }, "readinessProbe": { "exec": { @@ -177,7 +184,8 @@ {%- else %}{{ pgo_image_prefix }}/pgo-scheduler:{{ pgo_image_tag }} {%- endif %}", "securityContext": { - "allowPrivilegeEscalation": false + "allowPrivilegeEscalation": false, + "readOnlyRootFilesystem": true }, "livenessProbe": { "exec": { @@ -221,7 +229,12 @@ "value": "localhost:4150" } ], - "volumeMounts": [], + "volumeMounts": [ + { + "mountPath": "/tmp", + "name": "tmp" + } + ], "imagePullPolicy": "IfNotPresent" }, { "name": "event", @@ -229,7 +242,8 @@ {%- else %}{{ pgo_image_prefix }}/pgo-event:{{ pgo_image_tag }} {%- endif %}", "securityContext": { - "allowPrivilegeEscalation": false + "allowPrivilegeEscalation": false, + "readOnlyRootFilesystem": true }, "livenessProbe": { "httpGet": { @@ -245,11 +259,24 @@ "value": "3600" } ], - "volumeMounts": [], + "volumeMounts": [ + { + "mountPath": "/tmp", + "name": "tmp" + } + ], "imagePullPolicy": "IfNotPresent" } ], - "volumes": [] + "volumes": [ + { + "name": "tmp", + "emptyDir": { + "medium": "Memory", + "sizeLimit": "16Mi" + } + } + ] } } } diff --git a/installers/olm/postgresoperator.csv.yaml b/installers/olm/postgresoperator.csv.yaml index 02650a3f5b..3b1a864717 100644 --- a/installers/olm/postgresoperator.csv.yaml +++ b/installers/olm/postgresoperator.csv.yaml @@ -208,6 +208,7 @@ spec: imagePullPolicy: IfNotPresent securityContext: allowPrivilegeEscalation: false + readOnlyRootFilesystem: true ports: - containerPort: 8443 readinessProbe: @@ -232,12 +233,16 @@ spec: - { name: CRUNCHY_DEBUG, value: 'false' } - { name: EVENT_ADDR, value: 'localhost:4150' } - { name: PORT, value: '8443' } + volumeMounts: + - mountPath: /tmp + name: tmp - name: operator image: '${PGO_IMAGE_PREFIX}/postgres-operator:${PGO_IMAGE_TAG}' imagePullPolicy: IfNotPresent securityContext: allowPrivilegeEscalation: false + readOnlyRootFilesystem: true env: - { name: NAMESPACE, valueFrom: { fieldRef: { fieldPath: "metadata.annotations['olm.targetNamespaces']" } } } - { name: PGO_INSTALLATION_NAME, valueFrom: { fieldRef: { fieldPath: "metadata.namespace" } } } @@ -251,6 +256,7 @@ spec: imagePullPolicy: IfNotPresent securityContext: allowPrivilegeEscalation: false + readOnlyRootFilesystem: true livenessProbe: exec: command: [ @@ -269,12 +275,16 @@ spec: - { name: CRUNCHY_DEBUG, value: 'false' } - { name: EVENT_ADDR, value: 'localhost:4150' } - { name: TIMEOUT, value: '3600' } + volumeMounts: + - mountPath: /tmp + name: tmp - name: event image: '${PGO_IMAGE_PREFIX}/pgo-event:${PGO_IMAGE_TAG}' imagePullPolicy: IfNotPresent securityContext: allowPrivilegeEscalation: false + readOnlyRootFilesystem: true livenessProbe: httpGet: path: /ping @@ -283,3 +293,11 @@ spec: periodSeconds: 5 env: - { name: TIMEOUT, value: '3600' } + volumeMounts: + - mountPath: /tmp + name: tmp + volumes: + - name: tmp + emptyDir: + medium: Memory + sizeLimit: 16Mi From 7d497f9179f86ff274f6605127e363cfab988f43 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Fri, 5 Mar 2021 14:33:06 -0500 Subject: [PATCH 197/276] Explicitly set "privileged" security context to false While this is the default option in Kubernetes, this ensures that the Operator containers use this setting. --- deploy/deployment.json | 4 ++++ .../roles/pgo-operator/files/pgo-configs/backrest-job.json | 1 + .../pgo-operator/files/pgo-configs/cluster-bootstrap-job.json | 1 + .../pgo-operator/files/pgo-configs/cluster-deployment.json | 1 + .../roles/pgo-operator/files/pgo-configs/exporter.json | 1 + .../pgo-operator/files/pgo-configs/pgadmin-template.json | 1 + .../roles/pgo-operator/files/pgo-configs/pgbadger.json | 1 + .../pgo-operator/files/pgo-configs/pgbouncer-template.json | 1 + .../roles/pgo-operator/files/pgo-configs/pgdump-job.json | 1 + .../files/pgo-configs/pgo-backrest-repo-template.json | 1 + .../files/pgo-configs/pgo.sqlrunner-template.json | 1 + .../roles/pgo-operator/files/pgo-configs/pgrestore-job.json | 1 + .../roles/pgo-operator/files/pgo-configs/rmdata-job.json | 1 + .../ansible/roles/pgo-operator/templates/deployment.json.j2 | 4 ++++ installers/olm/postgresoperator.csv.yaml | 4 ++++ 15 files changed, 24 insertions(+) diff --git a/deploy/deployment.json b/deploy/deployment.json index 699536c75b..df247c515e 100644 --- a/deploy/deployment.json +++ b/deploy/deployment.json @@ -34,6 +34,7 @@ "imagePullPolicy": "IfNotPresent", "securityContext": { "allowPrivilegeEscalation": false, + "privileged": false, "readOnlyRootFilesystem": true }, "ports": [ @@ -123,6 +124,7 @@ "imagePullPolicy": "IfNotPresent", "securityContext": { "allowPrivilegeEscalation": false, + "privileged": false, "readOnlyRootFilesystem": true }, "readinessProbe": { @@ -179,6 +181,7 @@ "image": "$PGO_IMAGE_PREFIX/pgo-scheduler:$PGO_IMAGE_TAG", "securityContext": { "allowPrivilegeEscalation": false, + "privileged": false, "readOnlyRootFilesystem": true }, "livenessProbe": { @@ -236,6 +239,7 @@ "image": "$PGO_IMAGE_PREFIX/pgo-event:$PGO_IMAGE_TAG", "securityContext": { "allowPrivilegeEscalation": false, + "privileged": false, "readOnlyRootFilesystem": true }, "livenessProbe": { diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json index 9a52b1cf78..03dbaedc24 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json @@ -44,6 +44,7 @@ "image": "{{.CCPImagePrefix}}/crunchy-pgbackrest:{{.CCPImageTag}}", "securityContext": { "allowPrivilegeEscalation": false, + "privileged": false, "readOnlyRootFilesystem": true }, "volumeMounts": [ diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json index 2b898af652..1d3d226b35 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json @@ -31,6 +31,7 @@ "image": "{{.CCPImagePrefix}}/{{.CCPImage}}:{{.CCPImageTag}}", "securityContext": { "allowPrivilegeEscalation": false, + "privileged": false, "readOnlyRootFilesystem": true }, {{.ContainerResources}} diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json index a46cf7a472..03f488913d 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json @@ -44,6 +44,7 @@ "image": "{{.CCPImagePrefix}}/{{.CCPImage}}:{{.CCPImageTag}}", "securityContext": { "allowPrivilegeEscalation": false, + "privileged": false, "readOnlyRootFilesystem": true }, "readinessProbe": { diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/exporter.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/exporter.json index 846f3982cb..96fd44c9a7 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/exporter.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/exporter.json @@ -3,6 +3,7 @@ "image": "{{.PGOImagePrefix}}/crunchy-postgres-exporter:{{.PGOImageTag}}", "securityContext": { "allowPrivilegeEscalation": false, + "privileged": false, "readOnlyRootFilesystem": true }, "ports": [{ diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgadmin-template.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgadmin-template.json index 38cd7c32c8..2491fae5f5 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgadmin-template.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgadmin-template.json @@ -45,6 +45,7 @@ "image": "{{.CCPImagePrefix}}/crunchy-pgadmin4:{{.CCPImageTag}}", "securityContext": { "allowPrivilegeEscalation": false, + "privileged": false, "readOnlyRootFilesystem": true }, "ports": [{ diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbadger.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbadger.json index 9db32a07c6..da54728959 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbadger.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbadger.json @@ -3,6 +3,7 @@ "image": "{{.CCPImagePrefix}}/crunchy-pgbadger:{{.CCPImageTag}}", "securityContext": { "allowPrivilegeEscalation": false, + "privileged": false, "readOnlyRootFilesystem": true }, "ports": [ { diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer-template.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer-template.json index 43f2f31487..f22e920d97 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer-template.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer-template.json @@ -50,6 +50,7 @@ "image": "{{.CCPImagePrefix}}/crunchy-pgbouncer:{{.CCPImageTag}}", "securityContext": { "allowPrivilegeEscalation": false, + "privileged": false, "readOnlyRootFilesystem": true }, "ports": [{ diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgdump-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgdump-job.json index cebb3ddb8a..2865588975 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgdump-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgdump-job.json @@ -46,6 +46,7 @@ "image": "{{.CCPImagePrefix}}/crunchy-postgres-ha:{{.CCPImageTag}}", "securityContext": { "allowPrivilegeEscalation": false, + "privileged": false, "readOnlyRootFilesystem": true }, "command": ["/opt/crunchy/bin/uid_postgres.sh"], diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json index 187ea43bc6..baac0347b2 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json @@ -54,6 +54,7 @@ "image": "{{.CCPImagePrefix}}/crunchy-pgbackrest-repo:{{.CCPImageTag}}", "securityContext": { "allowPrivilegeEscalation": false, + "privileged": false, "readOnlyRootFilesystem": true }, "ports": [{ diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json index 98ff092e53..8312bb5ba3 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json @@ -33,6 +33,7 @@ "image": "{{.CCPImagePrefix}}/crunchy-postgres-ha:{{.CCPImageTag}}", "securityContext": { "allowPrivilegeEscalation": false, + "privileged": false, "readOnlyRootFilesystem": true }, "command": ["/opt/crunchy/bin/uid_postgres.sh"], diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json index cc52280075..1be775420e 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json @@ -47,6 +47,7 @@ "image": "{{.CCPImagePrefix}}/crunchy-postgres-ha:{{.CCPImageTag}}", "securityContext": { "allowPrivilegeEscalation": false, + "privileged": false, "readOnlyRootFilesystem": true }, "command": ["/opt/crunchy/bin/uid_postgres.sh"], diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/rmdata-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/rmdata-job.json index 55e681e2a9..f3908d884c 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/rmdata-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/rmdata-job.json @@ -32,6 +32,7 @@ "image": "{{.PGOImagePrefix}}/pgo-rmdata:{{.PGOImageTag}}", "securityContext": { "allowPrivilegeEscalation": false, + "privileged": false, "readOnlyRootFilesystem": true }, "env": [{ diff --git a/installers/ansible/roles/pgo-operator/templates/deployment.json.j2 b/installers/ansible/roles/pgo-operator/templates/deployment.json.j2 index 05241dd9b9..186491d845 100644 --- a/installers/ansible/roles/pgo-operator/templates/deployment.json.j2 +++ b/installers/ansible/roles/pgo-operator/templates/deployment.json.j2 @@ -36,6 +36,7 @@ "imagePullPolicy": "IfNotPresent", "securityContext": { "allowPrivilegeEscalation": false, + "privileged": false, "readOnlyRootFilesystem": true }, "ports": [ @@ -127,6 +128,7 @@ "imagePullPolicy": "IfNotPresent", "securityContext": { "allowPrivilegeEscalation": false, + "privileged": false, "readOnlyRootFilesystem": true }, "readinessProbe": { @@ -185,6 +187,7 @@ {%- endif %}", "securityContext": { "allowPrivilegeEscalation": false, + "privileged": false, "readOnlyRootFilesystem": true }, "livenessProbe": { @@ -243,6 +246,7 @@ {%- endif %}", "securityContext": { "allowPrivilegeEscalation": false, + "privileged": false, "readOnlyRootFilesystem": true }, "livenessProbe": { diff --git a/installers/olm/postgresoperator.csv.yaml b/installers/olm/postgresoperator.csv.yaml index 3b1a864717..d64dae80df 100644 --- a/installers/olm/postgresoperator.csv.yaml +++ b/installers/olm/postgresoperator.csv.yaml @@ -208,6 +208,7 @@ spec: imagePullPolicy: IfNotPresent securityContext: allowPrivilegeEscalation: false + privileged: false readOnlyRootFilesystem: true ports: - containerPort: 8443 @@ -242,6 +243,7 @@ spec: imagePullPolicy: IfNotPresent securityContext: allowPrivilegeEscalation: false + privileged: false readOnlyRootFilesystem: true env: - { name: NAMESPACE, valueFrom: { fieldRef: { fieldPath: "metadata.annotations['olm.targetNamespaces']" } } } @@ -256,6 +258,7 @@ spec: imagePullPolicy: IfNotPresent securityContext: allowPrivilegeEscalation: false + privileged: false readOnlyRootFilesystem: true livenessProbe: exec: @@ -284,6 +287,7 @@ spec: imagePullPolicy: IfNotPresent securityContext: allowPrivilegeEscalation: false + privileged: false readOnlyRootFilesystem: true livenessProbe: httpGet: From 77f4a753fd2fcef9bbc263310f4deae06b5f083d Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 9 Mar 2021 22:29:11 +0100 Subject: [PATCH 198/276] Fix Kubernetes misspellings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Marek Hanuš --- README.md | 2 +- installers/olm/description.openshift.md | 2 +- installers/olm/description.upstream.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1751080d53..3f4f4dc3b9 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ Use [pgBouncer][] for connection pooling #### Affinity and Tolerations -Have your PostgreSQL clusters deployed to [Kubernetes Nodes][k8s-nodes] of your preference with [node affinity][high-availability-node-affinity], or designate which nodes Kubernetes can schedule PostgreSQL instances to with Kubneretes [tolerations][high-availability-tolerations]. +Have your PostgreSQL clusters deployed to [Kubernetes Nodes][k8s-nodes] of your preference with [node affinity][high-availability-node-affinity], or designate which nodes Kubernetes can schedule PostgreSQL instances to with Kubernetes [tolerations][high-availability-tolerations]. #### Scheduled Backups diff --git a/installers/olm/description.openshift.md b/installers/olm/description.openshift.md index ca018e2cfa..df14addbab 100644 --- a/installers/olm/description.openshift.md +++ b/installers/olm/description.openshift.md @@ -17,7 +17,7 @@ providing the essential features you need to keep your PostgreSQL clusters up an - **Clone**: Create new clusters from your existing clusters or backups with a single [`pgo create cluster --restore-from`][pgo-create-cluster] command. - **TLS**: Secure communication between your applications and data servers by [enabling TLS for your PostgreSQL servers][pgo-task-tls], including the ability to enforce that all of your connections to use TLS. - **Connection Pooling**: Use [pgBouncer][] for connection pooling -- **Affinity and Tolerations**: Have your PostgreSQL clusters deployed to [Kubernetes Nodes][k8s-nodes] of your preference with [node affinity][high-availability-node-affinity], or designate which nodes Kubernetes can schedule PostgreSQL instances to with Kubneretes [tolerations][high-availability-tolerations]. +- **Affinity and Tolerations**: Have your PostgreSQL clusters deployed to [Kubernetes Nodes][k8s-nodes] of your preference with [node affinity][high-availability-node-affinity], or designate which nodes Kubernetes can schedule PostgreSQL instances to with Kubernetes [tolerations][high-availability-tolerations]. - **Full Customizability**: Crunchy PostgreSQL for OpenShift makes it easy to get your own PostgreSQL-as-a-Service up and running on and lets make further enhancements to customize your deployments, including: - Selecting different storage classes for your primary, replica, and backup storage diff --git a/installers/olm/description.upstream.md b/installers/olm/description.upstream.md index 989928f8b2..a9d28643e9 100644 --- a/installers/olm/description.upstream.md +++ b/installers/olm/description.upstream.md @@ -17,7 +17,7 @@ providing the essential features you need to keep your PostgreSQL clusters up an - **Clone**: Create new clusters from your existing clusters or backups with a single [`pgo create cluster --restore-from`][pgo-create-cluster] command. - **TLS**: Secure communication between your applications and data servers by [enabling TLS for your PostgreSQL servers][pgo-task-tls], including the ability to enforce that all of your connections to use TLS. - **Connection Pooling**: Use [pgBouncer][] for connection pooling -- **Affinity and Tolerations**: Have your PostgreSQL clusters deployed to [Kubernetes Nodes][k8s-nodes] of your preference with [node affinity][high-availability-node-affinity], or designate which nodes Kubernetes can schedule PostgreSQL instances to with Kubneretes [tolerations][high-availability-tolerations]. +- **Affinity and Tolerations**: Have your PostgreSQL clusters deployed to [Kubernetes Nodes][k8s-nodes] of your preference with [node affinity][high-availability-node-affinity], or designate which nodes Kubernetes can schedule PostgreSQL instances to with Kubernetes [tolerations][high-availability-tolerations]. - **Full Customizability**: Crunchy PostgreSQL for Kubernetes makes it easy to get your own PostgreSQL-as-a-Service up and running on and lets make further enhancements to customize your deployments, including: - Selecting different storage classes for your primary, replica, and backup storage From 27e8f3b8fdab3a126625b482e4ae13890b30253c Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Wed, 10 Mar 2021 17:37:16 -0500 Subject: [PATCH 199/276] Allow for startup to proceed even if status subresource is missing We have a guard to only allow a cluster to be started up if we know that it is in a shut down state. However, there appears to be cases independentof the Operator where the pgclusters.crunchydata.com "status" subresource is missing, which would cause that guard to be too aggressive. This change allows for the condition that there is no state registered in the status subresource, which allows for the start up to proceed regardless. Issue: [ch10811] --- .../controller/pgcluster/pgclustercontroller.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/internal/controller/pgcluster/pgclustercontroller.go b/internal/controller/pgcluster/pgclustercontroller.go index ef23c14b87..e53ff48350 100644 --- a/internal/controller/pgcluster/pgclustercontroller.go +++ b/internal/controller/pgcluster/pgclustercontroller.go @@ -191,11 +191,21 @@ func (c *Controller) onUpdate(oldObj, newObj interface{}) { // if the 'shutdown' parameter in the pgcluster update shows that the cluster should be either // shutdown or started but its current status does not properly reflect that it is, then // proceed with the logic needed to either shutdown or start the cluster + // + // we do need to check if the status has info in it. There have been cases + // where the entire status has been removed that could be external to the + // operator itself. In the case of checking that the state is in a shutdown + // phase, we also want to check if the status is completely empty. If it is, + // we will proceed with the shutdown. if newcluster.Spec.Shutdown && newcluster.Status.State != crv1.PgclusterStateShutdown { - _ = clusteroperator.ShutdownCluster(c.Client, *newcluster) + if err := clusteroperator.ShutdownCluster(c.Client, *newcluster); err != nil { + log.Error(err) + } } else if !newcluster.Spec.Shutdown && - newcluster.Status.State == crv1.PgclusterStateShutdown { - _ = clusteroperator.StartupCluster(c.Client, *newcluster) + (newcluster.Status.State == crv1.PgclusterStateShutdown || newcluster.Status.State == "") { + if err := clusteroperator.StartupCluster(c.Client, *newcluster); err != nil { + log.Error(err) + } } // check to see if autofail setting has been changed. If set to "true", it From 5dc8817eaecb1a3afb92240400a4512c7a6c24e6 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Thu, 11 Mar 2021 16:12:44 -0500 Subject: [PATCH 200/276] Bump v4.6.2 --- Makefile | 2 +- README.md | 2 +- bin/push-ccp-to-gcr.sh | 2 +- conf/postgres-operator/pgo.yaml | 4 +-- docs/config.toml | 2 +- docs/content/Configuration/compatibility.md | 6 ++++ docs/content/releases/4.6.2.md | 30 +++++++++++++++++++ examples/create-by-resource/fromcrd.json | 6 ++-- examples/envs.sh | 2 +- examples/helm/README.md | 4 +-- examples/helm/postgres/Chart.yaml | 2 +- .../helm/postgres/templates/pgcluster.yaml | 2 +- examples/helm/postgres/values.yaml | 2 +- examples/kustomize/createcluster/README.md | 16 +++++----- .../createcluster/base/pgcluster.yaml | 6 ++-- .../overlay/staging/hippo-rpl1-pgreplica.yaml | 2 +- installers/ansible/README.md | 2 +- installers/ansible/values.yaml | 6 ++-- installers/gcp-marketplace/Makefile | 2 +- installers/gcp-marketplace/README.md | 2 +- installers/gcp-marketplace/values.yaml | 6 ++-- installers/helm/Chart.yaml | 2 +- installers/helm/values.yaml | 6 ++-- installers/kubectl/client-setup.sh | 2 +- .../kubectl/postgres-operator-ocp311.yml | 8 ++--- installers/kubectl/postgres-operator.yml | 8 ++--- installers/metrics/ansible/README.md | 2 +- installers/metrics/helm/Chart.yaml | 2 +- installers/metrics/helm/helm_template.yaml | 2 +- installers/metrics/helm/values.yaml | 2 +- .../postgres-operator-metrics-ocp311.yml | 2 +- .../kubectl/postgres-operator-metrics.yml | 2 +- installers/olm/Makefile | 2 +- pkg/apis/crunchydata.com/v1/doc.go | 8 ++--- pkg/apiservermsgs/common.go | 2 +- redhat/atomic/help.1 | 2 +- redhat/atomic/help.md | 2 +- 37 files changed, 99 insertions(+), 63 deletions(-) create mode 100644 docs/content/releases/4.6.2.md diff --git a/Makefile b/Makefile index a97e454a80..ec25e785e9 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ PGOROOT ?= $(CURDIR) PGO_BASEOS ?= centos8 PGO_IMAGE_PREFIX ?= crunchydata PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) -PGO_VERSION ?= 4.6.1 +PGO_VERSION ?= 4.6.2 PGO_PG_VERSION ?= 13 PGO_PG_FULLVERSION ?= 13.2 PGO_BACKREST_VERSION ?= 2.31 diff --git a/README.md b/README.md index 3f4f4dc3b9..1deea34207 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,7 @@ Based on your storage settings in your Kubernetes environment, you may be able t ```shell kubectl create namespace pgo -kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.1/installers/kubectl/postgres-operator.yml +kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.2/installers/kubectl/postgres-operator.yml ``` Otherwise, we highly recommend following the instructions from our [Quickstart](https://access.crunchydata.com/documentation/postgres-operator/latest/quickstart/). diff --git a/bin/push-ccp-to-gcr.sh b/bin/push-ccp-to-gcr.sh index 8528843b36..60b0332e5a 100755 --- a/bin/push-ccp-to-gcr.sh +++ b/bin/push-ccp-to-gcr.sh @@ -16,7 +16,7 @@ GCR_IMAGE_PREFIX=gcr.io/crunchy-dev-test CCP_IMAGE_PREFIX=crunchydata -CCP_IMAGE_TAG=centos8-13.2-4.6.1 +CCP_IMAGE_TAG=centos8-13.2-4.6.2 IMAGES=( crunchy-prometheus diff --git a/conf/postgres-operator/pgo.yaml b/conf/postgres-operator/pgo.yaml index fcc3064ed7..ade19a59f2 100644 --- a/conf/postgres-operator/pgo.yaml +++ b/conf/postgres-operator/pgo.yaml @@ -2,7 +2,7 @@ Cluster: CCPImagePrefix: registry.developers.crunchydata.com/crunchydata Metrics: false Badger: false - CCPImageTag: centos8-13.2-4.6.1 + CCPImageTag: centos8-13.2-4.6.2 Port: 5432 PGBadgerPort: 10000 ExporterPort: 9187 @@ -80,4 +80,4 @@ Storage: Pgo: Audit: false PGOImagePrefix: registry.developers.crunchydata.com/crunchydata - PGOImageTag: centos8-4.6.1 + PGOImageTag: centos8-4.6.2 diff --git a/docs/config.toml b/docs/config.toml index e1aa049c8e..2bebb47add 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -25,7 +25,7 @@ disableNavChevron = false # set true to hide next/prev chevron, default is false highlightClientSide = false # set true to use highlight.pack.js instead of the default hugo chroma highlighter menushortcutsnewtab = true # set true to open shortcuts links to a new tab/window enableGitInfo = true -operatorVersion = "4.6.1" +operatorVersion = "4.6.2" postgresVersion = "13.2" postgresVersion13 = "13.2" postgresVersion12 = "12.6" diff --git a/docs/content/Configuration/compatibility.md b/docs/content/Configuration/compatibility.md index abc74a23b5..7be9a787e6 100644 --- a/docs/content/Configuration/compatibility.md +++ b/docs/content/Configuration/compatibility.md @@ -12,6 +12,12 @@ version dependencies between the two projects. Below are the operator releases a | Operator Release | Container Release | Postgres | PgBackrest Version |:----------|:-------------|:------------|:-------------- +| 4.6.2 | 4.6.2 | 13.2 | 2.31 | +|||12.6|2.31| +|||11.11|2.31| +|||10.16|2.31| +|||9.6.21|2.31| +|||| | 4.6.1 | 4.6.1 | 13.2 | 2.31 | |||12.6|2.31| |||11.11|2.31| diff --git a/docs/content/releases/4.6.2.md b/docs/content/releases/4.6.2.md new file mode 100644 index 0000000000..9df887f816 --- /dev/null +++ b/docs/content/releases/4.6.2.md @@ -0,0 +1,30 @@ +--- +title: "4.6.2" +date: +draft: false +weight: 58 +--- + +Crunchy Data announces the release of the PostgreSQL Operator 4.6.2 on March 17, 2021. + +The PostgreSQL Operator is released in conjunction with the [Crunchy Container Suite](https://github.com/CrunchyData/crunchy-containers/). + +PostgreSQL Operator 4.6.2 release includes the following software versions upgrades: + +- [Patroni](https://patroni.readthedocs.io/) is now at version 2.0.2. + +PostgreSQL Operator is tested against Kubernetes 1.17 - 1.20, OpenShift 3.11, OpenShift 4.4+, Google Kubernetes Engine (GKE), Amazon EKS, Microsoft AKS, and VMware Enterprise PKS 1.3+, and works on other Kubernetes distributions as well. + +## Changes + +- The Postgres Operator and associated containers now contain defaults to use more locked down Pod and Container security context settings. These include setting `readOnlyRootFileSystem` to `true`, `allowPrivilegeEscalation` to `false`, and explicitly stating that the container should not run as `root`. Many of these were already honored, if not defaulted, within the Postgres Operator ecosystem, but these changes make the settings explicit. This is all configuration: there are no breaking changes, and these configurations can be supported down to at least the 4.2 series. +- Revert setting "UsePAM" to "yes" by default as the bug fix in Docker that required that change was applied roughly one year ago. +- On Operator boot, Automatically detect when deployed in an OpenShift environment and set `DisableFSGroup` to `true`. This makes it easier to get started with the Postgres Operator in an OpenShift environment with the default security settings (i.e. `restricted`). If you use the `anyuid` Security Context Constraint, you will need to explicitly set `DisableFSGroup` to `false`. + +## Fixes + +- Ensure `archive_mode` is forced to `on` when performing using the "restore in place" method. This ensures that the timeline is correctly incremented post-restore, which could manifest itself with various types of WAL archive failures. +- Fix error when attempting to perform restores when using node affinity. Reported by (@gilfrade) and Cristian Chiru (@cristichiru). +- Fix issue where certain pgAdmin 4 functions did not work (e.g. taking a backup) due to `python` references in EL8 containers. Reported by (@douggutaby). +- Ensure a Postgres cluster shutdown can execute even if the `status` subresource of a `pgclusters.crunchydata.com` custom resource is missing. +- Ensure major upgrades via `crunchy-upgrade` support PostgreSQL 12 and PostgreSQL 13. Reported by (@lbartnicki92). diff --git a/examples/create-by-resource/fromcrd.json b/examples/create-by-resource/fromcrd.json index 6b8cb41f29..39e0b788fe 100644 --- a/examples/create-by-resource/fromcrd.json +++ b/examples/create-by-resource/fromcrd.json @@ -10,7 +10,7 @@ "deployment-name": "fromcrd", "name": "fromcrd", "pg-cluster": "fromcrd", - "pgo-version": "4.6.1", + "pgo-version": "4.6.2", "pgouser": "pgoadmin" }, "name": "fromcrd", @@ -45,7 +45,7 @@ "supplementalgroups": "" }, "ccpimage": "crunchy-postgres-ha", - "ccpimagetag": "centos8-13.2-4.6.1", + "ccpimagetag": "centos8-13.2-4.6.2", "clustername": "fromcrd", "database": "userdb", "exporterport": "9187", @@ -60,7 +60,7 @@ "port": "5432", "user": "testuser", "userlabels": { - "pgo-version": "4.6.1" + "pgo-version": "4.6.2" } } } diff --git a/examples/envs.sh b/examples/envs.sh index 43712b0f97..646fd06e90 100644 --- a/examples/envs.sh +++ b/examples/envs.sh @@ -20,7 +20,7 @@ export PGO_CONF_DIR=$PGOROOT/installers/ansible/roles/pgo-operator/files # the version of the Operator you run is set by these vars export PGO_IMAGE_PREFIX=registry.developers.crunchydata.com/crunchydata export PGO_BASEOS=centos8 -export PGO_VERSION=4.6.1 +export PGO_VERSION=4.6.2 export PGO_IMAGE_TAG=$PGO_BASEOS-$PGO_VERSION # for setting the pgo apiserver port, disabling TLS or not verifying TLS diff --git a/examples/helm/README.md b/examples/helm/README.md index f1fd384bc1..4bf8a57bb3 100644 --- a/examples/helm/README.md +++ b/examples/helm/README.md @@ -64,7 +64,7 @@ The following values can also be set: - `ha`: Whether or not to deploy a high availability PostgreSQL cluster. Can be either `true` or `false`, defaults to `false`. - `imagePrefix`: The prefix of the container images to use for this PostgreSQL cluster. Default to `registry.developers.crunchydata.com/crunchydata`. - `image`: The name of the container image to use for the PostgreSQL cluster. Defaults to `crunchy-postgres-ha`. -- `imageTag`: The container image tag to use. Defaults to `centos8-13.2-4.6.1`. +- `imageTag`: The container image tag to use. Defaults to `centos8-13.2-4.6.2`. - `memory`: The memory limit for the PostgreSQL cluster. Follows standard Kubernetes formatting. - `monitoring`: Whether or not to enable monitoring / metrics collection for this PostgreSQL instance. Can either be `true` or `false`, defaults to `false`. @@ -103,7 +103,7 @@ PGPASSWORD="W4tch0ut4hippo$" psql -h localhost -U hippo hippo ## Notes -Prior to PostgreSQL Operator 4.6.1, you will have to manually clean up some of the artifacts when running `helm uninstall`. +Prior to PostgreSQL Operator 4.6.2, you will have to manually clean up some of the artifacts when running `helm uninstall`. ## Additional Resources diff --git a/examples/helm/postgres/Chart.yaml b/examples/helm/postgres/Chart.yaml index d61868c39e..e393b955ff 100644 --- a/examples/helm/postgres/Chart.yaml +++ b/examples/helm/postgres/Chart.yaml @@ -20,4 +20,4 @@ version: 0.2.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. -appVersion: 4.6.1 +appVersion: 4.6.2 diff --git a/examples/helm/postgres/templates/pgcluster.yaml b/examples/helm/postgres/templates/pgcluster.yaml index 1f9097e7dd..6df3537199 100644 --- a/examples/helm/postgres/templates/pgcluster.yaml +++ b/examples/helm/postgres/templates/pgcluster.yaml @@ -28,7 +28,7 @@ spec: storagetype: dynamic ccpimage: {{ .Values.image | default "crunchy-postgres-ha" | quote }} ccpimageprefix: {{ .Values.imagePrefix | default "registry.developers.crunchydata.com/crunchydata" | quote }} - ccpimagetag: {{ .Values.imageTag | default "centos8-13.2-4.6.1" | quote }} + ccpimagetag: {{ .Values.imageTag | default "centos8-13.2-4.6.2" | quote }} clustername: {{ .Values.name | quote }} database: {{ .Values.name | quote }} {{- if .Values.monitoring }} diff --git a/examples/helm/postgres/values.yaml b/examples/helm/postgres/values.yaml index e5041f55de..a07e7dc2d3 100644 --- a/examples/helm/postgres/values.yaml +++ b/examples/helm/postgres/values.yaml @@ -10,5 +10,5 @@ password: W4tch0ut4hippo$ # ha: true # imagePrefix: registry.developers.crunchydata.com/crunchydata # image: crunchy-postgres-ha -# imageTag: centos8-13.2-4.6.1 +# imageTag: centos8-13.2-4.6.2 # memory: 1Gi diff --git a/examples/kustomize/createcluster/README.md b/examples/kustomize/createcluster/README.md index 9ddb3cd458..75d095ebbc 100644 --- a/examples/kustomize/createcluster/README.md +++ b/examples/kustomize/createcluster/README.md @@ -44,13 +44,13 @@ pgo show cluster hippo -n pgo ``` You will see something like this if successful: ``` -cluster : hippo (crunchy-postgres-ha:centos8-13.2-4.6.1) +cluster : hippo (crunchy-postgres-ha:centos8-13.2-4.6.2) pod : hippo-8fb6bd96-j87wq (Running) on gke-xxxx-default-pool-38e946bd-257w (1/1) (primary) pvc: hippo (1Gi) deployment : hippo deployment : hippo-backrest-shared-repo service : hippo - ClusterIP (10.0.56.86) - Ports (2022/TCP, 5432/TCP) - labels : pgo-version=4.6.1 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.2 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata ``` Feel free to run other pgo cli commands on the hippo cluster @@ -79,7 +79,7 @@ pgo show cluster dev-hippo -n pgo ``` You will see something like this if successful: ``` -cluster : dev-hippo (crunchy-postgres-ha:centos8-13.2-4.6.1) +cluster : dev-hippo (crunchy-postgres-ha:centos8-13.2-4.6.2) pod : dev-hippo-588d4cb746-bwrxb (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: dev-hippo (1Gi) deployment : dev-hippo @@ -87,7 +87,7 @@ cluster : dev-hippo (crunchy-postgres-ha:centos8-13.2-4.6.1) deployment : dev-hippo-pgbouncer service : dev-hippo - ClusterIP (10.0.62.87) - Ports (2022/TCP, 5432/TCP) service : dev-hippo-pgbouncer - ClusterIP (10.0.48.120) - Ports (5432/TCP) - labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.6.1 pgouser=admin + labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.6.2 pgouser=admin ``` #### staging The staging overlay will deploy a crunchy postgreSQL cluster with 2 replica's with annotations added @@ -113,7 +113,7 @@ pgo show cluster staging-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : staging-hippo (crunchy-postgres-ha:centos8-13.2-4.6.1) +cluster : staging-hippo (crunchy-postgres-ha:centos8-13.2-4.6.2) pod : staging-hippo-85cf6dcb65-9h748 (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: staging-hippo (1Gi) pod : staging-hippo-lnxw-cf47d8c8b-6r4wn (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (replica) @@ -128,7 +128,7 @@ cluster : staging-hippo (crunchy-postgres-ha:centos8-13.2-4.6.1) service : staging-hippo-replica - ClusterIP (10.0.56.57) - Ports (2022/TCP, 5432/TCP) pgreplica : staging-hippo-lnxw pgreplica : staging-hippo-rpl1 - labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.6.1 pgouser=admin vendor=crunchydata + labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.6.2 pgouser=admin vendor=crunchydata ``` #### production @@ -154,7 +154,7 @@ pgo show cluster prod-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : prod-hippo (crunchy-postgres-ha:centos8-13.2-4.6.1) +cluster : prod-hippo (crunchy-postgres-ha:centos8-13.2-4.6.2) pod : prod-hippo-5d6dd46497-rr67c (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (primary) pvc: prod-hippo (1Gi) pod : prod-hippo-flty-84d97c8769-2pzbh (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (replica) @@ -165,7 +165,7 @@ cluster : prod-hippo (crunchy-postgres-ha:centos8-13.2-4.6.1) service : prod-hippo - ClusterIP (10.0.56.18) - Ports (2022/TCP, 5432/TCP) service : prod-hippo-replica - ClusterIP (10.0.56.101) - Ports (2022/TCP, 5432/TCP) pgreplica : prod-hippo-flty - labels : pgo-version=4.6.1 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.2 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata ``` ### Delete the clusters To delete the clusters run the following pgo cli commands diff --git a/examples/kustomize/createcluster/base/pgcluster.yaml b/examples/kustomize/createcluster/base/pgcluster.yaml index 1f9773dfb6..cec2463c22 100644 --- a/examples/kustomize/createcluster/base/pgcluster.yaml +++ b/examples/kustomize/createcluster/base/pgcluster.yaml @@ -10,7 +10,7 @@ metadata: deployment-name: hippo name: hippo pg-cluster: hippo - pgo-version: 4.6.1 + pgo-version: 4.6.2 pgouser: admin name: hippo namespace: pgo @@ -46,7 +46,7 @@ spec: postgres: {} ccpimage: crunchy-postgres-ha ccpimageprefix: registry.developers.crunchydata.com/crunchydata - ccpimagetag: centos8-13.2-4.6.1 + ccpimagetag: centos8-13.2-4.6.2 clustername: hippo customconfig: "" database: hippo @@ -69,4 +69,4 @@ spec: port: "5432" user: hippo userlabels: - pgo-version: 4.6.1 + pgo-version: 4.6.2 diff --git a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml index 58d8fead81..32f1f62fd8 100644 --- a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml +++ b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml @@ -20,4 +20,4 @@ spec: storagetype: dynamic supplementalgroups: "" userlabels: - pgo-version: 4.6.1 + pgo-version: 4.6.2 diff --git a/installers/ansible/README.md b/installers/ansible/README.md index b41072a007..8755aea178 100644 --- a/installers/ansible/README.md +++ b/installers/ansible/README.md @@ -4,7 +4,7 @@ Crunchy Data

-Latest Release: 4.6.1 +Latest Release: 4.6.2 ## General diff --git a/installers/ansible/values.yaml b/installers/ansible/values.yaml index 5664377961..268d27d023 100644 --- a/installers/ansible/values.yaml +++ b/installers/ansible/values.yaml @@ -17,7 +17,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.2-4.6.1" +ccp_image_tag: "centos8-13.2-4.6.2" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -49,14 +49,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.1" +pgo_client_version: "4.6.2" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos8-4.6.1" +pgo_image_tag: "centos8-4.6.2" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/gcp-marketplace/Makefile b/installers/gcp-marketplace/Makefile index 060c017ff8..a931545979 100644 --- a/installers/gcp-marketplace/Makefile +++ b/installers/gcp-marketplace/Makefile @@ -6,7 +6,7 @@ MARKETPLACE_TOOLS ?= gcr.io/cloud-marketplace-tools/k8s/dev:$(MARKETPLACE_VERSIO MARKETPLACE_VERSION ?= 0.9.4 KUBECONFIG ?= $(HOME)/.kube/config PARAMETERS ?= {} -PGO_VERSION ?= 4.6.1 +PGO_VERSION ?= 4.6.2 IMAGE_BUILD_ARGS = --build-arg MARKETPLACE_VERSION='$(MARKETPLACE_VERSION)' \ --build-arg PGO_VERSION='$(PGO_VERSION)' diff --git a/installers/gcp-marketplace/README.md b/installers/gcp-marketplace/README.md index 13f4859557..5c4fb65043 100644 --- a/installers/gcp-marketplace/README.md +++ b/installers/gcp-marketplace/README.md @@ -59,7 +59,7 @@ Google Cloud Marketplace. ```shell IMAGE_REPOSITORY=gcr.io/crunchydata-public/postgres-operator - export PGO_VERSION=4.6.1 + export PGO_VERSION=4.6.2 export INSTALLER_IMAGE=${IMAGE_REPOSITORY}/deployer:${PGO_VERSION} export OPERATOR_IMAGE=${IMAGE_REPOSITORY}:${PGO_VERSION} export OPERATOR_IMAGE_API=${IMAGE_REPOSITORY}/pgo-apiserver:${PGO_VERSION} diff --git a/installers/gcp-marketplace/values.yaml b/installers/gcp-marketplace/values.yaml index f2722de252..d995c80fe2 100644 --- a/installers/gcp-marketplace/values.yaml +++ b/installers/gcp-marketplace/values.yaml @@ -10,7 +10,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.2-4.6.1" +ccp_image_tag: "centos8-13.2-4.6.2" create_rbac: "true" db_name: "" db_password_age_days: "0" @@ -32,9 +32,9 @@ pgo_admin_role_name: "pgoadmin" pgo_admin_username: "admin" pgo_client_container_install: "false" pgo_client_install: 'false' -pgo_client_version: "4.6.1" +pgo_client_version: "4.6.2" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.1" +pgo_image_tag: "centos8-4.6.2" pgo_installation_name: '${OPERATOR_NAME}' pgo_operator_namespace: '${OPERATOR_NAMESPACE}' scheduler_timeout: "3600" diff --git a/installers/helm/Chart.yaml b/installers/helm/Chart.yaml index bc1f55c408..bb973cf805 100644 --- a/installers/helm/Chart.yaml +++ b/installers/helm/Chart.yaml @@ -3,7 +3,7 @@ name: postgres-operator description: Crunchy PostgreSQL Operator Helm chart for Kubernetes type: application version: 0.2.0 -appVersion: 4.6.1 +appVersion: 4.6.2 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/crunchy_logo.png keywords: diff --git a/installers/helm/values.yaml b/installers/helm/values.yaml index 553f29a050..bb466303f1 100644 --- a/installers/helm/values.yaml +++ b/installers/helm/values.yaml @@ -37,7 +37,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.2-4.6.1" +ccp_image_tag: "centos8-13.2-4.6.2" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -69,14 +69,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.1" +pgo_client_version: "4.6.2" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos8-4.6.1" +pgo_image_tag: "centos8-4.6.2" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/kubectl/client-setup.sh b/installers/kubectl/client-setup.sh index ac82ddddc9..c1f077dda2 100755 --- a/installers/kubectl/client-setup.sh +++ b/installers/kubectl/client-setup.sh @@ -14,7 +14,7 @@ # This script should be run after the operator has been deployed PGO_OPERATOR_NAMESPACE="${PGO_OPERATOR_NAMESPACE:-pgo}" PGO_USER_ADMIN="${PGO_USER_ADMIN:-pgouser-admin}" -PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.1}" +PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.2}" PGO_CLIENT_URL="https://github.com/CrunchyData/postgres-operator/releases/download/${PGO_CLIENT_VERSION}" PGO_CMD="${PGO_CMD-kubectl}" diff --git a/installers/kubectl/postgres-operator-ocp311.yml b/installers/kubectl/postgres-operator-ocp311.yml index a944ed2d93..780a1d1ada 100644 --- a/installers/kubectl/postgres-operator-ocp311.yml +++ b/installers/kubectl/postgres-operator-ocp311.yml @@ -44,7 +44,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos8-13.2-4.6.1" + ccp_image_tag: "centos8-13.2-4.6.2" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -76,14 +76,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.1" + pgo_client_version: "4.6.2" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos8-4.6.1" + pgo_image_tag: "centos8-4.6.2" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -160,7 +160,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.1 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.2 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/kubectl/postgres-operator.yml b/installers/kubectl/postgres-operator.yml index 87a144b7a0..e643ff20c6 100644 --- a/installers/kubectl/postgres-operator.yml +++ b/installers/kubectl/postgres-operator.yml @@ -139,7 +139,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos8-13.2-4.6.1" + ccp_image_tag: "centos8-13.2-4.6.2" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -172,14 +172,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.1" + pgo_client_version: "4.6.2" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos8-4.6.1" + pgo_image_tag: "centos8-4.6.2" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -269,7 +269,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.1 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.2 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/ansible/README.md b/installers/metrics/ansible/README.md index 1a0c731b73..ca5f797410 100644 --- a/installers/metrics/ansible/README.md +++ b/installers/metrics/ansible/README.md @@ -4,7 +4,7 @@ Crunchy Data

-Latest Release: 4.6.1 +Latest Release: 4.6.2 ## General diff --git a/installers/metrics/helm/Chart.yaml b/installers/metrics/helm/Chart.yaml index 44296af626..9f659af095 100644 --- a/installers/metrics/helm/Chart.yaml +++ b/installers/metrics/helm/Chart.yaml @@ -3,6 +3,6 @@ name: postgres-operator-monitoring description: Install for Crunchy PostgreSQL Operator Monitoring type: application version: 0.2.0 -appVersion: 4.6.1 +appVersion: 4.6.2 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/crunchy_logo.png diff --git a/installers/metrics/helm/helm_template.yaml b/installers/metrics/helm/helm_template.yaml index bcf1fca573..8926e386b1 100644 --- a/installers/metrics/helm/helm_template.yaml +++ b/installers/metrics/helm/helm_template.yaml @@ -20,5 +20,5 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.1" +pgo_image_tag: "centos8-4.6.2" diff --git a/installers/metrics/helm/values.yaml b/installers/metrics/helm/values.yaml index f0461cbadc..2ff05b4a10 100644 --- a/installers/metrics/helm/values.yaml +++ b/installers/metrics/helm/values.yaml @@ -20,7 +20,7 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.1" +pgo_image_tag: "centos8-4.6.2" # ===================== # Configuration Options diff --git a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml index 12dbdd62ef..276e80914c 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml @@ -96,7 +96,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.1 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.2 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/kubectl/postgres-operator-metrics.yml b/installers/metrics/kubectl/postgres-operator-metrics.yml index 071e3b6331..8d88788c43 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics.yml @@ -165,7 +165,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.1 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.2 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/olm/Makefile b/installers/olm/Makefile index 23d4e140bd..b4455d4a9e 100644 --- a/installers/olm/Makefile +++ b/installers/olm/Makefile @@ -11,7 +11,7 @@ OLM_TOOLS ?= registry.localhost:5000/postgres-operator-olm-tools:$(OLM_SDK_VERSI OLM_VERSION ?= 0.15.1 PGO_BASEOS ?= centos8 PGO_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -PGO_VERSION ?= 4.6.1 +PGO_VERSION ?= 4.6.2 PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) CCP_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(PGO_VERSION) CCP_POSTGIS_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(CCP_POSTGIS_VERSION)-$(PGO_VERSION) diff --git a/pkg/apis/crunchydata.com/v1/doc.go b/pkg/apis/crunchydata.com/v1/doc.go index 14cf9279a7..3661730abe 100644 --- a/pkg/apis/crunchydata.com/v1/doc.go +++ b/pkg/apis/crunchydata.com/v1/doc.go @@ -53,7 +53,7 @@ cluster. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.1", + '{"ClientVersion":"4.6.2", "Namespace":"pgouser1", "Name":"mycluster", $PGO_APISERVER_URL/clusters @@ -72,7 +72,7 @@ show all of the clusters that are in the given namespace. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.1", + '{"ClientVersion":"4.6.2", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/showclusters @@ -82,7 +82,7 @@ $PGO_APISERVER_URL/showclusters curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.1", + '{"ClientVersion":"4.6.2", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/clustersdelete @@ -90,7 +90,7 @@ $PGO_APISERVER_URL/clustersdelete Schemes: http, https BasePath: / - Version: 4.6.1 + Version: 4.6.2 License: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0 Contact: Crunchy Data https://www.crunchydata.com/ diff --git a/pkg/apiservermsgs/common.go b/pkg/apiservermsgs/common.go index f312e0fff7..d57e12b9e2 100644 --- a/pkg/apiservermsgs/common.go +++ b/pkg/apiservermsgs/common.go @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -const PGO_VERSION = "4.6.1" +const PGO_VERSION = "4.6.2" // Ok status const Ok = "ok" diff --git a/redhat/atomic/help.1 b/redhat/atomic/help.1 index f7b98114d3..20c51a73c5 100644 --- a/redhat/atomic/help.1 +++ b/redhat/atomic/help.1 @@ -56,4 +56,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa \fB\fCRelease=\fR .PP -The specific release number of the container. For example, Release="4.6.1" +The specific release number of the container. For example, Release="4.6.2" diff --git a/redhat/atomic/help.md b/redhat/atomic/help.md index 0e013b84a0..9e8350338a 100644 --- a/redhat/atomic/help.md +++ b/redhat/atomic/help.md @@ -45,4 +45,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa `Release=` -The specific release number of the container. For example, Release="4.6.1" +The specific release number of the container. For example, Release="4.6.2" From d6372ad5fd6abbe5bc23a764be34b32765ce74c9 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 15 Mar 2021 14:15:00 -0400 Subject: [PATCH 201/276] Revert "Fully support readOnlyRootFilesystem for each deployed container" There are issues running with this configuration when using the default "restricted" SecurityContextConstraint in an OpenShift environment. Rather than applying this configuration in a patch release, this will be addressed in a newer version. While this patch does work in essentially every other environment, in order to maintain maximum compatibiltiy with the default SCC, it makes sense to revert it. This reverts commit 000f58c1e755dfc525141afc571f6a08a4eeb062. --- deploy/deployment.json | 43 ++++--------------- .../files/pgo-configs/backrest-job.json | 14 +----- .../pgo-configs/cluster-bootstrap-job.json | 25 +---------- .../files/pgo-configs/cluster-deployment.json | 25 +---------- .../files/pgo-configs/exporter.json | 9 +--- .../files/pgo-configs/pgadmin-template.json | 34 ++------------- .../files/pgo-configs/pgbadger.json | 7 +-- .../files/pgo-configs/pgbouncer-template.json | 14 +----- .../files/pgo-configs/pgdump-job.json | 17 ++------ .../pgo-backrest-repo-template.json | 39 +---------------- .../pgo-configs/pgo.sqlrunner-template.json | 3 +- .../files/pgo-configs/pgrestore-job.json | 14 +----- .../files/pgo-configs/rmdata-job.json | 3 +- .../pgo-operator/templates/deployment.json.j2 | 43 ++++--------------- installers/olm/postgresoperator.csv.yaml | 18 -------- 15 files changed, 34 insertions(+), 274 deletions(-) diff --git a/deploy/deployment.json b/deploy/deployment.json index df247c515e..8e7880b4d0 100644 --- a/deploy/deployment.json +++ b/deploy/deployment.json @@ -34,8 +34,7 @@ "imagePullPolicy": "IfNotPresent", "securityContext": { "allowPrivilegeEscalation": false, - "privileged": false, - "readOnlyRootFilesystem": true + "privileged": false }, "ports": [ { "containerPort": $PGO_APISERVER_PORT } @@ -112,20 +111,14 @@ "value": "localhost:4150" } ], - "volumeMounts": [ - { - "mountPath": "/tmp", - "name": "tmp" - } - ] + "volumeMounts": [] }, { "name": "operator", "image": "$PGO_IMAGE_PREFIX/postgres-operator:$PGO_IMAGE_TAG", "imagePullPolicy": "IfNotPresent", "securityContext": { "allowPrivilegeEscalation": false, - "privileged": false, - "readOnlyRootFilesystem": true + "privileged": false }, "readinessProbe": { "exec": { @@ -181,8 +174,7 @@ "image": "$PGO_IMAGE_PREFIX/pgo-scheduler:$PGO_IMAGE_TAG", "securityContext": { "allowPrivilegeEscalation": false, - "privileged": false, - "readOnlyRootFilesystem": true + "privileged": false }, "livenessProbe": { "exec": { @@ -226,12 +218,7 @@ "value": "localhost:4150" } ], - "volumeMounts": [ - { - "mountPath": "/tmp", - "name": "tmp" - } - ], + "volumeMounts": [], "imagePullPolicy": "IfNotPresent" }, { @@ -239,8 +226,7 @@ "image": "$PGO_IMAGE_PREFIX/pgo-event:$PGO_IMAGE_TAG", "securityContext": { "allowPrivilegeEscalation": false, - "privileged": false, - "readOnlyRootFilesystem": true + "privileged": false }, "livenessProbe": { "httpGet": { @@ -256,24 +242,11 @@ "value": "3600" } ], - "volumeMounts": [ - { - "mountPath": "/tmp", - "name": "tmp" - } - ], + "volumeMounts": [], "imagePullPolicy": "IfNotPresent" } ], - "volumes": [ - { - "name": "tmp", - "emptyDir": { - "medium": "Memory", - "sizeLimit": "16Mi" - } - } - ] + "volumes": [] } } } diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json index 03dbaedc24..cb0821d40f 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/backrest-job.json @@ -25,13 +25,6 @@ }, "spec": { "volumes": [ - { - "name": "tmp", - "emptyDir": { - "medium": "Memory", - "sizeLimit": "16Mi" - } - }{{ if .PgbackrestRestoreVolumes }},{{ end }} {{.PgbackrestRestoreVolumes}} ], "securityContext": {{.SecurityContext}}, @@ -44,14 +37,9 @@ "image": "{{.CCPImagePrefix}}/crunchy-pgbackrest:{{.CCPImageTag}}", "securityContext": { "allowPrivilegeEscalation": false, - "privileged": false, - "readOnlyRootFilesystem": true + "privileged": false }, "volumeMounts": [ - { - "mountPath": "/tmp", - "name": "tmp" - }{{ if .PgbackrestRestoreVolumeMounts }},{{ end }} {{.PgbackrestRestoreVolumeMounts}} ], "env": [{ diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json index 1d3d226b35..2afd0605f8 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-bootstrap-job.json @@ -31,8 +31,7 @@ "image": "{{.CCPImagePrefix}}/{{.CCPImage}}:{{.CCPImageTag}}", "securityContext": { "allowPrivilegeEscalation": false, - "privileged": false, - "readOnlyRootFilesystem": true + "privileged": false }, {{.ContainerResources}} "env": [{ @@ -139,14 +138,6 @@ }, { "mountPath": "/dev/shm", "name": "dshm" - }, - { - "mountPath": "/tmp", - "name": "tmp" - }, - { - "mountPath": "/var/lib/pgsql/.ssh", - "name": "pgbackrest-ssh" }, { "mountPath": "/etc/pgbackrest/conf.d", "name": "pgbackrest-config" @@ -179,13 +170,6 @@ "secretName": "{{.RestoreFrom}}-backrest-repo-config" } }, - { - "name": "pgbackrest-ssh", - "emptyDir": { - "medium": "Memory", - "sizeLimit": "128Ki" - } - }, {{if .TLSEnabled}} { "name": "tls-server", @@ -235,13 +219,6 @@ } ] } - }, - { - "name": "tmp", - "emptyDir": { - "medium": "Memory", - "sizeLimit": "16Mi" - } } {{.TablespaceVolumes}}], "affinity": { diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json index 03f488913d..70164d94be 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/cluster-deployment.json @@ -44,8 +44,7 @@ "image": "{{.CCPImagePrefix}}/{{.CCPImage}}:{{.CCPImageTag}}", "securityContext": { "allowPrivilegeEscalation": false, - "privileged": false, - "readOnlyRootFilesystem": true + "privileged": false }, "readinessProbe": { "exec": { @@ -193,14 +192,6 @@ { "mountPath": "/etc/podinfo", "name": "podinfo" - }, - { - "mountPath": "/tmp", - "name": "tmp" - }, - { - "mountPath": "/var/lib/pgsql/.ssh", - "name": "pgbackrest-ssh" } {{.TablespaceVolumeMounts}} ], @@ -304,20 +295,6 @@ "medium": "Memory" } }, - { - "name": "tmp", - "emptyDir": { - "medium": "Memory", - "sizeLimit": "16Mi" - } - }, - { - "name": "pgbackrest-ssh", - "emptyDir": { - "medium": "Memory", - "sizeLimit": "128Ki" - } - }, { "name": "pgbackrest-config", "projected": { "sources": [] } diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/exporter.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/exporter.json index 96fd44c9a7..8473963a55 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/exporter.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/exporter.json @@ -3,8 +3,7 @@ "image": "{{.PGOImagePrefix}}/crunchy-postgres-exporter:{{.PGOImageTag}}", "securityContext": { "allowPrivilegeEscalation": false, - "privileged": false, - "readOnlyRootFilesystem": true + "privileged": false }, "ports": [{ "containerPort": {{.ExporterPort}}, @@ -54,11 +53,5 @@ } } } - ], - "volumeMounts": [ - { - "mountPath": "/tmp", - "name": "tmp" - } ] } diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgadmin-template.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgadmin-template.json index 2491fae5f5..dabb807e1e 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgadmin-template.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgadmin-template.json @@ -45,8 +45,7 @@ "image": "{{.CCPImagePrefix}}/crunchy-pgadmin4:{{.CCPImageTag}}", "securityContext": { "allowPrivilegeEscalation": false, - "privileged": false, - "readOnlyRootFilesystem": true + "privileged": false }, "ports": [{ "containerPort": {{.Port}}, @@ -60,37 +59,12 @@ "value": "{{.InitPass}}" }], "volumeMounts": [{ - "name": "tmp", - "mountPath": "/tmp" - }, - { - "name": "pgadmin-log", - "mountPath": "/var/log/pgadmin" - }, - { - "name": "tmp", - "mountPath": "/etc/httpd/run" - }, - { "name": "pgadmin-datadir", - "mountPath": "/var/lib/pgadmin" - }] + "mountPath": "/var/lib/pgadmin", + "readOnly": false + }] }], "volumes": [{ - "name": "tmp", - "emptyDir": { - "medium": "Memory", - "sizeLimit": "16Mi" - } - }, - { - "name": "pgadmin-log", - "emptyDir": { - "medium": "Memory", - "sizeLimit": "16Mi" - } - }, - { "name": "pgadmin-datadir", "persistentVolumeClaim": { "claimName": "{{.PVCName}}" diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbadger.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbadger.json index da54728959..26214a6bdd 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbadger.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbadger.json @@ -3,8 +3,7 @@ "image": "{{.CCPImagePrefix}}/crunchy-pgbadger:{{.CCPImageTag}}", "securityContext": { "allowPrivilegeEscalation": false, - "privileged": false, - "readOnlyRootFilesystem": true + "privileged": false }, "ports": [ { "containerPort": {{.PGBadgerPort}}, @@ -32,10 +31,6 @@ } }, "volumeMounts": [ - { - "mountPath": "/tmp", - "name": "tmp" - }, { "mountPath": "/pgdata", "name": "pgdata", diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer-template.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer-template.json index f22e920d97..3f797120fb 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer-template.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgbouncer-template.json @@ -50,8 +50,7 @@ "image": "{{.CCPImagePrefix}}/crunchy-pgbouncer:{{.CCPImageTag}}", "securityContext": { "allowPrivilegeEscalation": false, - "privileged": false, - "readOnlyRootFilesystem": true + "privileged": false }, "ports": [{ "containerPort": {{.Port}}, @@ -71,10 +70,6 @@ "value": "{{.PrimaryServiceName}}" }], "volumeMounts": [ - { - "mountPath": "/tmp", - "name": "tmp" - }, {{if .TLSEnabled}} { "mountPath": "/pgconf/tls/pgbouncer", @@ -89,13 +84,6 @@ ] }], "volumes": [ - { - "name": "tmp", - "emptyDir": { - "medium": "Memory", - "sizeLimit": "1Mi" - } - }, {{if .TLSEnabled}} { "name": "tls-pgbouncer", diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgdump-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgdump-job.json index 2865588975..3749b4ed20 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgdump-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgdump-job.json @@ -22,13 +22,6 @@ }, "spec": { "volumes": [ - { - "name": "tmp", - "emptyDir": { - "medium": "Memory", - "sizeLimit": "1Mi" - } - }, { "name": "pgdata", "persistentVolumeClaim": { @@ -46,19 +39,15 @@ "image": "{{.CCPImagePrefix}}/crunchy-postgres-ha:{{.CCPImageTag}}", "securityContext": { "allowPrivilegeEscalation": false, - "privileged": false, - "readOnlyRootFilesystem": true + "privileged": false }, "command": ["/opt/crunchy/bin/uid_postgres.sh"], "args": ["/opt/crunchy/bin/start.sh"], "volumeMounts": [ - { - "mountPath": "/tmp", - "name": "tmp" - }, { "mountPath": "/pgdata", - "name": "pgdata" + "name": "pgdata", + "readOnly": false } ], "env": [ diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json index baac0347b2..9f5f6a88b0 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-repo-template.json @@ -54,8 +54,7 @@ "image": "{{.CCPImagePrefix}}/crunchy-pgbackrest-repo:{{.CCPImageTag}}", "securityContext": { "allowPrivilegeEscalation": false, - "privileged": false, - "readOnlyRootFilesystem": true + "privileged": false }, "ports": [{ "containerPort": {{.SshdPort}}, @@ -105,20 +104,7 @@ "name": "backrestrepo", "mountPath": "/backrestrepo", "readOnly": false - }, - { - "name": "tmp", - "mountPath": "/tmp" - }, - { - "name": "pgbackrest-home", - "mountPath": "/home/pgbackrest" - }, - { - "name": "pgbackrest-conf", - "mountPath": "/etc/pgbackrest" - } - ] + }] }], "volumes": [{ "name": "sshd", @@ -130,27 +116,6 @@ "persistentVolumeClaim": { "claimName": "{{.BackrestRepoClaimName}}" } - }, - { - "name": "tmp", - "emptyDir": { - "medium": "Memory", - "sizeLimit": "64Mi" - } - }, - { - "name": "pgbackrest-home", - "emptyDir": { - "medium": "Memory", - "sizeLimit": "128Ki" - } - }, - { - "name": "pgbackrest-conf", - "emptyDir": { - "medium": "Memory", - "sizeLimit": "128Ki" - } }], "affinity": { {{.PodAntiAffinity}} diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json index 8312bb5ba3..b0493ba6d9 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo.sqlrunner-template.json @@ -33,8 +33,7 @@ "image": "{{.CCPImagePrefix}}/crunchy-postgres-ha:{{.CCPImageTag}}", "securityContext": { "allowPrivilegeEscalation": false, - "privileged": false, - "readOnlyRootFilesystem": true + "privileged": false }, "command": ["/opt/crunchy/bin/uid_postgres.sh"], "args": ["/opt/crunchy/bin/start.sh"], diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json index 1be775420e..477c9e5305 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgrestore-job.json @@ -22,13 +22,6 @@ }, "spec": { "volumes": [ - { - "name": "tmp", - "emptyDir": { - "medium": "Memory", - "sizeLimit": "1Mi" - } - }, { "name": "pgdata", "persistentVolumeClaim": { @@ -47,16 +40,11 @@ "image": "{{.CCPImagePrefix}}/crunchy-postgres-ha:{{.CCPImageTag}}", "securityContext": { "allowPrivilegeEscalation": false, - "privileged": false, - "readOnlyRootFilesystem": true + "privileged": false }, "command": ["/opt/crunchy/bin/uid_postgres.sh"], "args": ["/opt/crunchy/bin/start.sh"], "volumeMounts": [ - { - "mountPath": "/tmp", - "name": "tmp" - }, { "mountPath": "/pgdata", "name": "pgdata", diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/rmdata-job.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/rmdata-job.json index f3908d884c..a9dbafe96a 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/rmdata-job.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/rmdata-job.json @@ -32,8 +32,7 @@ "image": "{{.PGOImagePrefix}}/pgo-rmdata:{{.PGOImageTag}}", "securityContext": { "allowPrivilegeEscalation": false, - "privileged": false, - "readOnlyRootFilesystem": true + "privileged": false }, "env": [{ "name": "PG_CLUSTER", diff --git a/installers/ansible/roles/pgo-operator/templates/deployment.json.j2 b/installers/ansible/roles/pgo-operator/templates/deployment.json.j2 index 186491d845..adeb88f8a3 100644 --- a/installers/ansible/roles/pgo-operator/templates/deployment.json.j2 +++ b/installers/ansible/roles/pgo-operator/templates/deployment.json.j2 @@ -36,8 +36,7 @@ "imagePullPolicy": "IfNotPresent", "securityContext": { "allowPrivilegeEscalation": false, - "privileged": false, - "readOnlyRootFilesystem": true + "privileged": false }, "ports": [ { "containerPort": {{ pgo_apiserver_port }} } @@ -114,12 +113,7 @@ "value": "localhost:4150" } ], - "volumeMounts": [ - { - "mountPath": "/tmp", - "name": "tmp" - } - ] + "volumeMounts": [] }, { "name": "operator", "image": "{% if pgo_image | default('') != '' %}{{ pgo_image }} @@ -128,8 +122,7 @@ "imagePullPolicy": "IfNotPresent", "securityContext": { "allowPrivilegeEscalation": false, - "privileged": false, - "readOnlyRootFilesystem": true + "privileged": false }, "readinessProbe": { "exec": { @@ -187,8 +180,7 @@ {%- endif %}", "securityContext": { "allowPrivilegeEscalation": false, - "privileged": false, - "readOnlyRootFilesystem": true + "privileged": false }, "livenessProbe": { "exec": { @@ -232,12 +224,7 @@ "value": "localhost:4150" } ], - "volumeMounts": [ - { - "mountPath": "/tmp", - "name": "tmp" - } - ], + "volumeMounts": [], "imagePullPolicy": "IfNotPresent" }, { "name": "event", @@ -246,8 +233,7 @@ {%- endif %}", "securityContext": { "allowPrivilegeEscalation": false, - "privileged": false, - "readOnlyRootFilesystem": true + "privileged": false }, "livenessProbe": { "httpGet": { @@ -263,24 +249,11 @@ "value": "3600" } ], - "volumeMounts": [ - { - "mountPath": "/tmp", - "name": "tmp" - } - ], + "volumeMounts": [], "imagePullPolicy": "IfNotPresent" } ], - "volumes": [ - { - "name": "tmp", - "emptyDir": { - "medium": "Memory", - "sizeLimit": "16Mi" - } - } - ] + "volumes": [] } } } diff --git a/installers/olm/postgresoperator.csv.yaml b/installers/olm/postgresoperator.csv.yaml index d64dae80df..76c89a0602 100644 --- a/installers/olm/postgresoperator.csv.yaml +++ b/installers/olm/postgresoperator.csv.yaml @@ -209,7 +209,6 @@ spec: securityContext: allowPrivilegeEscalation: false privileged: false - readOnlyRootFilesystem: true ports: - containerPort: 8443 readinessProbe: @@ -234,9 +233,6 @@ spec: - { name: CRUNCHY_DEBUG, value: 'false' } - { name: EVENT_ADDR, value: 'localhost:4150' } - { name: PORT, value: '8443' } - volumeMounts: - - mountPath: /tmp - name: tmp - name: operator image: '${PGO_IMAGE_PREFIX}/postgres-operator:${PGO_IMAGE_TAG}' @@ -244,7 +240,6 @@ spec: securityContext: allowPrivilegeEscalation: false privileged: false - readOnlyRootFilesystem: true env: - { name: NAMESPACE, valueFrom: { fieldRef: { fieldPath: "metadata.annotations['olm.targetNamespaces']" } } } - { name: PGO_INSTALLATION_NAME, valueFrom: { fieldRef: { fieldPath: "metadata.namespace" } } } @@ -259,7 +254,6 @@ spec: securityContext: allowPrivilegeEscalation: false privileged: false - readOnlyRootFilesystem: true livenessProbe: exec: command: [ @@ -278,9 +272,6 @@ spec: - { name: CRUNCHY_DEBUG, value: 'false' } - { name: EVENT_ADDR, value: 'localhost:4150' } - { name: TIMEOUT, value: '3600' } - volumeMounts: - - mountPath: /tmp - name: tmp - name: event image: '${PGO_IMAGE_PREFIX}/pgo-event:${PGO_IMAGE_TAG}' @@ -288,7 +279,6 @@ spec: securityContext: allowPrivilegeEscalation: false privileged: false - readOnlyRootFilesystem: true livenessProbe: httpGet: path: /ping @@ -297,11 +287,3 @@ spec: periodSeconds: 5 env: - { name: TIMEOUT, value: '3600' } - volumeMounts: - - mountPath: /tmp - name: tmp - volumes: - - name: tmp - emptyDir: - medium: Memory - sizeLimit: 16Mi From 4391162bb99ab92efc634eb7abb7ac8780bfc30c Mon Sep 17 00:00:00 2001 From: Andrew L'Ecuyer Date: Mon, 15 Mar 2021 20:23:00 +0000 Subject: [PATCH 202/276] Fix UsePAM Setting in sshd_config File Fixes the UsePAM setting in sshd_config by changing the value from 'No' to 'no'. --- .../roles/pgo-operator/files/pgo-backrest-repo/sshd_config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/sshd_config b/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/sshd_config index f79f039771..5a0f61e8f9 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/sshd_config +++ b/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/sshd_config @@ -82,7 +82,7 @@ ChallengeResponseAuthentication yes # This is set explicitly to *no* as we are only using pubkey authentication and # because each container is isolated to only an unprivileged user. -UsePAM No +UsePAM no #AllowAgentForwarding yes #AllowTcpForwarding yes From 7fca436ec56ab3a6aecd34be8d87902ba897119d Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 15 Mar 2021 21:56:35 -0400 Subject: [PATCH 203/276] Updates to the 4.6.2 release notes. --- docs/content/releases/4.6.2.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/content/releases/4.6.2.md b/docs/content/releases/4.6.2.md index 9df887f816..1ab27e4c97 100644 --- a/docs/content/releases/4.6.2.md +++ b/docs/content/releases/4.6.2.md @@ -5,19 +5,20 @@ draft: false weight: 58 --- -Crunchy Data announces the release of the PostgreSQL Operator 4.6.2 on March 17, 2021. +Crunchy Data announces the release of the PostgreSQL Operator 4.6.2 on March 19, 2021. The PostgreSQL Operator is released in conjunction with the [Crunchy Container Suite](https://github.com/CrunchyData/crunchy-containers/). PostgreSQL Operator 4.6.2 release includes the following software versions upgrades: - [Patroni](https://patroni.readthedocs.io/) is now at version 2.0.2. +- [pgBouncer](https://www.pgbouncer.org/) for CentOS 8 / UBI 8 is rebuilt to use the libc for its async DNS backend. PostgreSQL Operator is tested against Kubernetes 1.17 - 1.20, OpenShift 3.11, OpenShift 4.4+, Google Kubernetes Engine (GKE), Amazon EKS, Microsoft AKS, and VMware Enterprise PKS 1.3+, and works on other Kubernetes distributions as well. ## Changes -- The Postgres Operator and associated containers now contain defaults to use more locked down Pod and Container security context settings. These include setting `readOnlyRootFileSystem` to `true`, `allowPrivilegeEscalation` to `false`, and explicitly stating that the container should not run as `root`. Many of these were already honored, if not defaulted, within the Postgres Operator ecosystem, but these changes make the settings explicit. This is all configuration: there are no breaking changes, and these configurations can be supported down to at least the 4.2 series. +- The Postgres Operator and associated containers now contain defaults to use more locked down Pod and Container security context settings. These include setting `allowPrivilegeEscalation` to `false` and explicitly stating that the container should not run as `root`. Many of these were already honored, if not defaulted, within the Postgres Operator ecosystem, but these changes make the settings explicit. This is all configuration: there are no breaking changes, and these configurations can be supported down to at least the 4.2 series. - Revert setting "UsePAM" to "yes" by default as the bug fix in Docker that required that change was applied roughly one year ago. - On Operator boot, Automatically detect when deployed in an OpenShift environment and set `DisableFSGroup` to `true`. This makes it easier to get started with the Postgres Operator in an OpenShift environment with the default security settings (i.e. `restricted`). If you use the `anyuid` Security Context Constraint, you will need to explicitly set `DisableFSGroup` to `false`. From 74b15ee197f2ab95f329cebc27cde24c9fa7d506 Mon Sep 17 00:00:00 2001 From: andrewlecuyer <43458182+andrewlecuyer@users.noreply.github.com> Date: Wed, 17 Mar 2021 10:49:49 -0500 Subject: [PATCH 204/276] Fixes ReplicaSet & Pod Logs Permissions in OLM CSV Fixes the ReplicaSet and pods/logs permissions defined in the PostgreSQL Operator ClusterServiceVersion spec. This aligns the clusterPermissions defined in the CSV with the RBAC generated via other supported installation methods. Issue: [ch10871] Issue: #2334 --- installers/olm/postgresoperator.csv.yaml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/installers/olm/postgresoperator.csv.yaml b/installers/olm/postgresoperator.csv.yaml index 76c89a0602..fd7c5a6371 100644 --- a/installers/olm/postgresoperator.csv.yaml +++ b/installers/olm/postgresoperator.csv.yaml @@ -107,8 +107,6 @@ spec: - endpoints - pods - pods/exec - - pods/log - - replicasets - secrets - services - persistentvolumeclaims @@ -121,10 +119,19 @@ spec: - update - delete - deletecollection + - apiGroups: + - '' + resources: + - pods/log + verbs: + - get + - list + - watch - apiGroups: - apps resources: - deployments + - replicasets verbs: - get - list From 804c81a9a41b653b72dbb551fa8316f1ca2855ff Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Thu, 18 Mar 2021 12:51:10 -0400 Subject: [PATCH 205/276] Documentation updates Documentation updates in prep for the upcoming releaes. --- docs/layouts/partials/flex/body-aftercontent.html | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 docs/layouts/partials/flex/body-aftercontent.html diff --git a/docs/layouts/partials/flex/body-aftercontent.html b/docs/layouts/partials/flex/body-aftercontent.html new file mode 100644 index 0000000000..42949426a4 --- /dev/null +++ b/docs/layouts/partials/flex/body-aftercontent.html @@ -0,0 +1,3 @@ +
+

© 2017 - 2021 Crunchy Data Solutions, Inc.

+
From d8c0705f809e106cf90ad7247f1c24a766f3f4da Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Thu, 18 Mar 2021 14:39:43 -0400 Subject: [PATCH 206/276] Updates to the 4.6.2 release notes --- docs/content/releases/4.6.2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/releases/4.6.2.md b/docs/content/releases/4.6.2.md index 1ab27e4c97..2cc890e38f 100644 --- a/docs/content/releases/4.6.2.md +++ b/docs/content/releases/4.6.2.md @@ -5,7 +5,7 @@ draft: false weight: 58 --- -Crunchy Data announces the release of the PostgreSQL Operator 4.6.2 on March 19, 2021. +Crunchy Data announces the release of the PostgreSQL Operator 4.6.2 on March 22, 2021. The PostgreSQL Operator is released in conjunction with the [Crunchy Container Suite](https://github.com/CrunchyData/crunchy-containers/). From 8f408e18d35d16f40f7137236b57b5cd2a75ac9b Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 22 Mar 2021 11:03:39 -0400 Subject: [PATCH 207/276] Clarify different Pod anti-affinity options in docs It is possible to fine tune the Pod anti-affinity settings for the managed Deployments in a PostgreSQL cluster, though this was not particularly clear in that section of the docs. Issue: #2342 --- .../architecture/high-availability/_index.md | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/content/architecture/high-availability/_index.md b/docs/content/architecture/high-availability/_index.md index a2ab9b0148..08eb2da4c2 100644 --- a/docs/content/architecture/high-availability/_index.md +++ b/docs/content/architecture/high-availability/_index.md @@ -219,6 +219,30 @@ number of nodes are available to support this configuration, certain deployments will fail, since it will not be possible for Kubernetes to successfully schedule the pods for each deployment. +It is possible to fine tune the pod anti-affinity rules further, specifically, +set different affinity rules for the PostgreSQL, pgBackRest, and pgBouncer +Deployments. These can be handled by the following flags on [`pgo create cluster`]({{< relref "pgo-client/reference/pgo_create_cluster.md">}}): + +- `--pod-anti-affinity`: Sets the pod anti-affinity rules for all the managed +Deployments in the cluster (PostgreSQL, pgBackRest, pgBouncer) +- `--pod-anti-affinity-pgbackrest`: Sets the pod anti-affinity rules for _only_ +the pgBackRest Deployment. This takes precedence over the value of +`--pod-anti-affinity`. +- `--pod-anti-affinity-pgbouncer`: Sets the pod anti-affinity rules for _only_ +the pgBouncer Deployment. This takes precedence over the value of +`--pod-anti-affinity`. + +For example, to use `required` pod anti-affinity between PostgreSQL instances +but use only `preferred` anti-affinity for pgBackRest and pgBouncer, you could +use the following command: + +``` +pgo create cluster hippo --replicas=2 --pgbouncer \ + --pod-anti-affinity=required \ + --pod-anti-affinity=preferred \ + --pod-anti-afinity=preferred +``` + ## Synchronous Replication: Guarding Against Transactions Loss Clusters managed by the Crunchy PostgreSQL Operator can be deployed with From 725a8dafb643f1313886c76a40359264851769ff Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 22 Mar 2021 15:22:35 -0400 Subject: [PATCH 208/276] Fix issue with documentation updates Some of the interactive features were not loading due to the last round of documentation updates. This fixes those issues. --- .../partials/flex/body-aftercontent.html | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/docs/layouts/partials/flex/body-aftercontent.html b/docs/layouts/partials/flex/body-aftercontent.html index 42949426a4..8d5f330f56 100644 --- a/docs/layouts/partials/flex/body-aftercontent.html +++ b/docs/layouts/partials/flex/body-aftercontent.html @@ -1,3 +1,44 @@ +
+ {{ partial "next-prev-page.html" . }} +
+ + + + +
+ +{{ partial "flex/scripts.html" . }} From 93937d8c35020a41f4f0527b1822bd893ed587c6 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Wed, 24 Mar 2021 12:15:15 -0400 Subject: [PATCH 209/276] Update descriptions for the different backrest/backup storage configs Some of the legacy purposes of what this parameter did, which was both confusing and inaccurate. This adds some clarity. Reported by: Andreas Karlsson --- docs/content/Configuration/pgo-yaml-configuration.md | 4 ++-- docs/content/installation/configuration.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/content/Configuration/pgo-yaml-configuration.md b/docs/content/Configuration/pgo-yaml-configuration.md index b9467258d3..63f5812c71 100644 --- a/docs/content/Configuration/pgo-yaml-configuration.md +++ b/docs/content/Configuration/pgo-yaml-configuration.md @@ -44,9 +44,9 @@ The *pgo.yaml* file is broken into major sections as described below: | Setting|Definition | |---|---| |PrimaryStorage |required, the value of the storage configuration to use for the primary PostgreSQL deployment -|BackupStorage |required, the value of the storage configuration to use for backups, including the storage for pgbackrest repo volumes |ReplicaStorage |required, the value of the storage configuration to use for the replica PostgreSQL deployments -|BackrestStorage |required, the value of the storage configuration to use for the pgbackrest shared repository deployment created when a user specifies pgbackrest to be enabled on a cluster +|BackrestStorage |required, the value of the storage configuration to use for the pgBackRest repository. +|BackupStorage |required, the value of the storage configuration to use for backups generated by `pg_dump`. |WALStorage | optional, the value of the storage configuration to use for PostgreSQL Write Ahead Log |StorageClass | optional, for a dynamic storage type, you can specify the storage class used for storage provisioning (e.g. standard, gold, fast) |AccessMode |the access mode for new PVCs (e.g. ReadWriteMany, ReadWriteOnce, ReadOnlyMany). See below for descriptions of these. diff --git a/docs/content/installation/configuration.md b/docs/content/installation/configuration.md index 1ce7d34808..52e69be444 100644 --- a/docs/content/installation/configuration.md +++ b/docs/content/installation/configuration.md @@ -134,8 +134,8 @@ class available in your environment. | Name | Default | Required | Description | |------|---------|----------|-------------| -| `backrest_storage` | default | **Required** | Set the value of the storage configuration to use for the pgbackrest shared repository deployment created when a user specifies pgbackrest to be enabled on a cluster. | -| `backup_storage` | default | **Required** | Set the value of the storage configuration to use for backups, including the storage for pgbackrest repo volumes. | +| `backrest_storage` | default | **Required** | Set the value of the storage configuration to use for the pgBackRest repository. | +| `backup_storage` | default | **Required** | required, the value of the storage configuration to use for backups generated by `pg_dump`. | | `primary_storage` | default | **Required** | Set to configure which storage definition to use when creating volumes used by PostgreSQL primaries on all newly created clusters. | | `replica_storage` | default | **Required** | Set to configure which storage definition to use when creating volumes used by PostgreSQL replicas on all newly created clusters. | | `wal_storage` | | | Set to configure which storage definition to use when creating volumes used for PostgreSQL Write-Ahead Log. | From 6eb194a54fe87eb91377b95e24f4f7f61ca4d4d9 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Wed, 24 Mar 2021 16:28:30 -0400 Subject: [PATCH 210/276] Add clarification on disabling FSGroup for metrics installation This makes it abundantly clear that you may need to toggle the "disable_fsgroup" attribute in the PostgreSQL Operator Monitoring stack installer based upon the type of OpenShift / CRC environment you are deploying into. --- .../metrics/metrics-configuration.md | 2 +- .../other/ansible/metrics-prerequisites.md | 8 ++++++-- .../installation/metrics/other/helm-metrics.md | 13 +++++++++---- .../metrics/postgres-operator-metrics.md | 16 +++++++++++++--- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/docs/content/installation/metrics/metrics-configuration.md b/docs/content/installation/metrics/metrics-configuration.md index 1c86eb7819..e90a7ff88b 100644 --- a/docs/content/installation/metrics/metrics-configuration.md +++ b/docs/content/installation/metrics/metrics-configuration.md @@ -25,7 +25,7 @@ These variables affect the general configuration of PostgreSQL Operator Monitori | `create_rbac` | true | **Required** | Set to true if the installer should create the RBAC resources required to run the PostgreSQL Operator Monitoring infrastructure. | | `db_port` | 5432 | **Required** | Set to configure the PostgreSQL port used by all PostgreSQL clusters. | | `delete_metrics_namespace` | false | | Set to configure whether or not the metrics namespace (defined using variable `metrics_namespace`) is deleted when uninstalling the monitoring infrastructure. | -| `disable_fsgroup` | false | | Set to `true` for deployments where you do not want to have the default PostgreSQL fsGroup (26) set. The typical usage is in OpenShift environments that have a `restricted` Security Context Constraints. | +| `disable_fsgroup` | false | | Set to `true` for deployments where you do not want to have the default PostgreSQL fsGroup (26) set. The typical usage is in OpenShift environments that have a `restricted` Security Context Constraints. If you use the `anyuid` SCC, you would want to set this to `false`. The Postgres Operator will set this value appropriately by default, except for when using the `anyuid` SCC. | | `grafana_admin_password` | admin | **Required** | Set to configure the login password for the Grafana administrator. | | `grafana_admin_username` | admin | **Required** | Set to configure the login username for the Grafana administrator. | | `grafana_install` | true | **Required** | Set to true to install Grafana to visualize metrics. | diff --git a/docs/content/installation/metrics/other/ansible/metrics-prerequisites.md b/docs/content/installation/metrics/other/ansible/metrics-prerequisites.md index 1e9d31164d..116559ea30 100644 --- a/docs/content/installation/metrics/other/ansible/metrics-prerequisites.md +++ b/docs/content/installation/metrics/other/ansible/metrics-prerequisites.md @@ -62,7 +62,6 @@ if you are being using them for your environment. Both sets of variables cannot be used at the same time. The unused variables should be left commented out or removed. {{% /notice %}} - | Name | Default | Required | Description | |-----------------------------------|-------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `kubernetes_context` | | **Required**, if deploying to Kubernetes |When deploying to Kubernetes, set to configure the context name of the kubeconfig to be used for authentication. | @@ -83,10 +82,15 @@ kubectl config current-context ## Configuring - `values.yaml` The `values.yaml` file contains all of the configuration parameters -for deploying the PostgreSQL Operator Monitoring infrastructure. +for deploying the PostgreSQL Operator Monitoring infrastructure. The [example file](https://github.com/CrunchyData/postgres-operator/blob/v{{< param operatorVersion >}}/installers/metrics/ansible/values.yaml) contains defaults that should work in most Kubernetes environments, but it may require some customization. +Note that in OpenShift and CodeReady Containers you will need to set the +`disable_fsgroup` to `true` attribute to `true` if you are using the +`restricted` Security Context Constraint (SCC). If you are using the `anyuid` +SCC, you will need to set `disable_fsgroup` to `false`. + For a detailed description of each configuration parameter, please read the [PostgreSQL Operator Installer Metrics Configuration Reference](<{{< relref "/installation/metrics/metrics-configuration.md">}}>) diff --git a/docs/content/installation/metrics/other/helm-metrics.md b/docs/content/installation/metrics/other/helm-metrics.md index fb94918003..e09a1ae5c8 100644 --- a/docs/content/installation/metrics/other/helm-metrics.md +++ b/docs/content/installation/metrics/other/helm-metrics.md @@ -57,10 +57,15 @@ file will be used to populate the configuation options in the ConfigMap. ### Configuration - `values.yaml` The `values.yaml` file contains all of the configuration parameters for deploying -the PostgreSQL Operator Monitoring infrastructure. +the PostgreSQL Operator Monitoring infrastructure. The [values.yaml file](https://github.com/CrunchyData/postgres-operator/blob/master/installers/metrics/helm/values.yaml) contains the defaults that should work in most Kubernetes environments, but it may require some customization. +Note that in OpenShift and CodeReady Containers you will need to set the +`disable_fsgroup` to `true` attribute to `true` if you are using the +`restricted` Security Context Constraint (SCC). If you are using the `anyuid` +SCC, you will need to set `disable_fsgroup` to `false`. + For a detailed description of each configuration parameter, please read the [PostgreSQL Operator Monitoring Installer Configuration Reference](<{{< relref "/installation/metrics/metrics-configuration.md">}}>) @@ -81,11 +86,11 @@ upgrade and uninstall the PostgreSQL Operator. ## Upgrade and Uninstall -Once install has be completed using Helm, it will also be used to upgrade and +Once install has be completed using Helm, it will also be used to upgrade and uninstall your PostgreSQL Operator. {{% notice tip %}} -The `name` and `namespace` in the following sections should match the options +The `name` and `namespace` in the following sections should match the options provided at install. {{% /notice %}} @@ -111,7 +116,7 @@ helm uninstall -n ## Debugging -When the `pgo-deployer` job does not complete successfully, the resources that +When the `pgo-deployer` job does not complete successfully, the resources that are created and normally cleaned up by Helm will be left in your Kubernetes cluster. This will allow you to use the failed job and its logs to debug the issue. The following command will show the logs for the `pgo-deployer` diff --git a/docs/content/installation/metrics/postgres-operator-metrics.md b/docs/content/installation/metrics/postgres-operator-metrics.md index a077862ba9..2440ebfa66 100644 --- a/docs/content/installation/metrics/postgres-operator-metrics.md +++ b/docs/content/installation/metrics/postgres-operator-metrics.md @@ -18,6 +18,11 @@ kubectl create namespace pgo kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v{{< param operatorVersion >}}/installers/metrics/kubectl/postgres-operator-metrics.yml ``` +Note that in OpenShift and CodeReady Containers you will need to set the +`disable_fsgroup` to `true` attribute to `true` if you are using the +`restricted` Security Context Constraint (SCC). If you are using the `anyuid` +SCC, you will need to set `disable_fsgroup` to `false`. + However, we still advise that you read onward to see how to properly configure the PostgreSQL Operator Monitoring infrastructure. @@ -53,13 +58,13 @@ environmental requirements. By default, the `pgo-deployer` uses a ServiceAccount called `pgo-metrics-deployer-sa` that has a ClusterRoleBinding (`pgo-metrics-deployer-crb`) with several ClusterRole permissions. This ClusterRole is needed for the initial configuration and deployment -of the various applications comprising the monitoring infrastructure. This includes permissions +of the various applications comprising the monitoring infrastructure. This includes permissions to create: * RBAC for use by Prometheus and/or Grafana * The metrics namespace -The required list of privileges are available in the +The required list of privileges are available in the [postgres-operator-metrics.yml](https://raw.githubusercontent.com/CrunchyData/postgres-operator/v{{< param operatorVersion >}}/installers/metrics/kubectl/postgres-operator-metrics.yml) file: @@ -95,6 +100,11 @@ for deploying PostgreSQL Operator Monitoring. The [example file](https://github. contains defaults that should work in most Kubernetes environments, but it may require some customization. +Note that in OpenShift and CodeReady Containers you will need to set the +`disable_fsgroup` to `true` attribute to `true` if you are using the +`restricted` Security Context Constraint (SCC). If you are using the `anyuid` +SCC, you will need to set `disable_fsgroup` to `false`. + For a detailed description of each configuration parameter, please read the [PostgreSQL Operator Monitoring Installer Configuration Reference](<{{< relref "/installation/metrics/metrics-configuration.md">}}>) @@ -103,7 +113,7 @@ For a detailed description of each configuration parameter, please read the The deploy job can be used to perform different deployment actions for the PostgreSQL Operator Monitoring infrastructure. When you run the job it will install the monitoring infrastructure by default but you can change the deployment action to -uninstall or update. The `DEPLOY_ACTION` environment variable in the `postgres-operator-metrics.yml` +uninstall or update. The `DEPLOY_ACTION` environment variable in the `postgres-operator-metrics.yml` file can be set to `install-metrics`, `update-metrics`, and `uninstall-metrics`. ### Image Pull Secrets From 8629817da2e346f115af8e9c6708527775bcf7f3 Mon Sep 17 00:00:00 2001 From: andrewlecuyer <43458182+andrewlecuyer@users.noreply.github.com> Date: Sat, 3 Apr 2021 12:45:32 -0500 Subject: [PATCH 211/276] Update UsePAM During Operator Upgrade The sshd_config file is now properly upgraded if needed during a PostgreSQL Operator upgrade. Specifically, the the UsePAM setting is set to 'no' if currently set to 'yes' to align with the default sshd_config deployed with the PostgreSQL Operator as of version 4.6.1. --- internal/operator/backrest/repo.go | 3 +- internal/operator/cluster/upgrade.go | 42 +++++++++++++++++++++++++++- internal/util/cluster.go | 21 +++++++------- 3 files changed, 54 insertions(+), 12 deletions(-) diff --git a/internal/operator/backrest/repo.go b/internal/operator/backrest/repo.go index f1b06b4756..0195a8debf 100644 --- a/internal/operator/backrest/repo.go +++ b/internal/operator/backrest/repo.go @@ -165,12 +165,13 @@ func CreateRepoDeployment(clientset kubernetes.Interface, cluster *crv1.Pgcluste // // If the Secret already exists, then missing fields will be overwritten. func CreateRepoSecret(clientset kubernetes.Interface, cluster *crv1.Pgcluster) error { - return util.CreateBackrestRepoSecrets(clientset, + _, err := util.CreateBackrestRepoSecrets(clientset, util.BackrestRepoConfig{ ClusterName: cluster.Name, ClusterNamespace: cluster.Namespace, OperatorNamespace: operator.PgoNamespace, }) + return err } // setBootstrapRepoOverrides overrides certain fields used to populate the pgBackRest repository template diff --git a/internal/operator/cluster/upgrade.go b/internal/operator/cluster/upgrade.go index ec979db6da..c5a600815b 100644 --- a/internal/operator/cluster/upgrade.go +++ b/internal/operator/cluster/upgrade.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" "io/ioutil" + "regexp" "strconv" "strings" "time" @@ -51,6 +52,11 @@ const ( postgresGISHAImage = "crunchy-postgres-gis-ha" ) +// nssWrapperRegex is the regular expression that is utilized to determine if the UsePAM +// setting is set to 'yes' in the sshd_config (as it might be for versions up to v4.6.1, +// v4.5.2 and v4.4.3) +var usePAMRegex = regexp.MustCompile(`(?im)^UsePAM\s*yes`) + // AddUpgrade implements the upgrade workflow in accordance with the received pgtask // the general process is outlined below: // 1) get the existing pgcluster CRD instance that matches the name provided in the pgtask @@ -468,12 +474,21 @@ func recreateBackrestRepoSecret(clientset kubernetes.Interface, clustername, nam } } + var repoSecret *v1.Secret if err == nil { - err = util.CreateBackrestRepoSecrets(clientset, config) + repoSecret, err = util.CreateBackrestRepoSecrets(clientset, config) } if err != nil { log.Errorf("error generating new backrest repo secrets during pgcluster upgrade: %v", err) } + + if err := updatePGBackRestSSHDConfig(clientset, repoSecret, namespace); err != nil { + log.Errorf("error upgrading pgBackRest sshd_config: %v", err) + } + + if err != nil { + log.Errorf("error generating new backrest repo secrets during pgcluster upgrade: %v", err) + } } // preparePgclusterForUpgrade specifically updates the existing CRD instance to set correct values @@ -929,3 +944,28 @@ func updateClusterConfig(clientset kubeapi.Interface, pgcluster *crv1.Pgcluster, // sync the changes to the configmap to the DCS return pgoconfig.NewDCS(patchedClusterConfig, clientset, pgcluster.GetObjectMeta().GetLabels()[config.LABEL_PGHA_SCOPE]).Sync() } + +// updatePGBackRestSSHDConfig is responsible for upgrading the sshd_config file as needed across +// operator versions to ensure proper functionality with pgBackRest +func updatePGBackRestSSHDConfig(clientset kubernetes.Interface, repoSecret *v1.Secret, + namespace string) error { + + ctx := context.TODO() + updatedRepoSecret := repoSecret.DeepCopy() + + // For versions prior to v4.6.2, the UsePAM setting might be set to 'yes' as previously + // required to workaround a known Docker issue. Since this issue has since been resolved, + // we now want to ensure this setting is set to 'no'. + if !usePAMRegex.MatchString(string(updatedRepoSecret.Data["sshd_config"])) { + return nil + } + + updatedRepoSecret.Data["sshd_config"] = + []byte(usePAMRegex.ReplaceAllString(string(updatedRepoSecret.Data["sshd_config"]), + "UsePAM no")) + + _, err := clientset.CoreV1().Secrets(namespace).Update(ctx, updatedRepoSecret, + metav1.UpdateOptions{}) + + return err +} diff --git a/internal/util/cluster.go b/internal/util/cluster.go index 25d85c4609..03328b23af 100644 --- a/internal/util/cluster.go +++ b/internal/util/cluster.go @@ -125,7 +125,7 @@ var cmdStopPostgreSQL = []string{ // CreateBackrestRepoSecrets creates the secrets required to manage the // pgBackRest repo container func CreateBackrestRepoSecrets(clientset kubernetes.Interface, - backrestRepoConfig BackrestRepoConfig) error { + backrestRepoConfig BackrestRepoConfig) (*v1.Secret, error) { ctx := context.TODO() // first: determine if a Secret already exists. If it does, we are going to @@ -138,7 +138,7 @@ func CreateBackrestRepoSecrets(clientset kubernetes.Interface, // only return an error if this is a **not** a not found error if secretErr != nil && !kerrors.IsNotFound(secretErr) { log.Error(secretErr) - return secretErr + return nil, secretErr } // determine if we need to create a new secret, i.e. this is a not found error @@ -166,7 +166,7 @@ func CreateBackrestRepoSecrets(clientset kubernetes.Interface, if configErr != nil { log.Error(configErr) - return configErr + return nil, configErr } // set the SSH/SSHD configuration, if it is not presently set @@ -185,7 +185,7 @@ func CreateBackrestRepoSecrets(clientset kubernetes.Interface, if keyErr != nil { log.Error(keyErr) - return keyErr + return nil, keyErr } secret.Data[backRestRepoSecretKeyAuthorizedKeys] = keys.Public @@ -225,16 +225,17 @@ func CreateBackrestRepoSecrets(clientset kubernetes.Interface, } // time to create or update the secret! + var repoSecret *v1.Secret + var err error if newSecret { - _, err := clientset.CoreV1().Secrets(backrestRepoConfig.ClusterNamespace).Create( + repoSecret, err = clientset.CoreV1().Secrets(backrestRepoConfig.ClusterNamespace).Create( ctx, secret, metav1.CreateOptions{}) - return err + } else { + repoSecret, err = clientset.CoreV1().Secrets(backrestRepoConfig.ClusterNamespace).Update( + ctx, secret, metav1.UpdateOptions{}) } - _, err := clientset.CoreV1().Secrets(backrestRepoConfig.ClusterNamespace).Update( - ctx, secret, metav1.UpdateOptions{}) - - return err + return repoSecret, err } // CreateRMDataTask is a legacy method that was moved into this file. This From 606fe9e4d8898423e0a597b349b2550557e0a870 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 5 Apr 2021 12:27:34 -0400 Subject: [PATCH 212/276] Update general descriptions This provides some updates around the geneal description for PGO. --- README.md | 109 +++++++++++++++++++++++++---------------- docs/config.toml | 2 +- docs/content/_index.md | 69 ++++++++++++++++---------- 3 files changed, 111 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index 1deea34207..d0cc0c0b03 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,22 @@ -

Crunchy Data PostgreSQL Operator

+

PGO: The Postgres Operator from Crunchy Data

Crunchy Data

[![Go Report Card](https://goreportcard.com/badge/github.com/CrunchyData/postgres-operator)](https://goreportcard.com/report/github.com/CrunchyData/postgres-operator) -# Run your own production-grade PostgreSQL-as-a-Service on Kubernetes! +# Run Cloud Native PostgreSQL on Kubernetes with PGO: The Postgres Operator from Crunchy Data! -The [Crunchy PostgreSQL Operator][documentation] automates and simplifies deploying and managing -open source PostgreSQL clusters on Kubernetes and other Kubernetes-enabled Platforms by providing -the essential features you need to keep your PostgreSQL clusters up and running, including: +[PGO][documentation], the [Postgres Operator][documentation] developed by +[Crunchy Data](https://crunchydata.com/) and included in [Crunchy PostgreSQL for Kubernetes](https://www.crunchydata.com/products/crunchy-postgresql-for-kubernetes/), automates and simplifies deploying and managing open source +PostgreSQL clusters on Kubernetes. -#### PostgreSQL Cluster [Provisioning][provisioning] +Whether you need to get a simple Postgres cluster up and running, need to deploy +a high availability, fault tolerant cluster in production, or are running your +own database-as-a-service, the PostgreSQL Operator provides the essential +features you need to keep your cloud native Postgres clusters healthy, including: + +#### Postgres Cluster [Provisioning][provisioning] [Create, Scale, & Delete PostgreSQL clusters with ease][provisioning], while fully customizing your Pods and PostgreSQL configuration! @@ -33,7 +38,7 @@ Set how long you want your backups retained for. Works great with very large dat #### TLS Secure communication between your applications and data servers by [enabling TLS for your PostgreSQL servers][pgo-task-tls], -including the ability to enforce that all of your connections to use TLS. +including the ability to enforce all of your connections to use TLS. #### [Monitoring][monitoring] @@ -76,16 +81,22 @@ the S3 protocol. The PostgreSQL Operator can backup, restore, and create new clu #### Multi-Namespace Support -You can control how the PostgreSQL Operator leverages [Kubernetes Namespaces][k8s-namespaces] with several different deployment models: +You can control how PGO, the Postgres Operator, leverages [Kubernetes Namespaces][k8s-namespaces] with several different deployment models: -- Deploy the PostgreSQL Operator and all PostgreSQL clusters to the same namespace -- Deploy the PostgreSQL Operator to one namespaces, and all PostgreSQL clusters to a different namespace -- Deploy the PostgreSQL Operator to one namespace, and have your PostgreSQL clusters managed across multiple namespaces -- Dynamically add and remove namespaces managed by the PostgreSQL Operator using the `pgo create namespace` and `pgo delete namespace` commands +- Deploy PGO and all PostgreSQL clusters to the same namespace +- Deploy PGO to one namespaces, and all PostgreSQL clusters to a different +namespace +- Deploy PGO to one namespace, and have your PostgreSQL clusters managed across +multiple namespaces +- Dynamically add and remove namespaces managed by the PostgreSQL Operator using +the `pgo` client to run `pgo create namespace` and `pgo delete namespace` #### Full Customizability -The Crunchy PostgreSQL Operator makes it easy to get your own PostgreSQL-as-a-Service up and running on Kubernetes-enabled platforms, but we know that there are further customizations that you can make. As such, the Crunchy PostgreSQL Operator allows you to further customize your deployments, including: +The Postgres Operator (PGO) makes it easy to get Postgres up and running on +Kubernetes-enabled platforms, but we know that there are further customizations +that you can make. As such, PGO allows you to further customize your +deployments, including: - Selecting different storage classes for your primary, replica, and backup storage - Select your own container resources class for each PostgreSQL cluster deployment; differentiate between resources applied for primary and replica clusters! @@ -94,7 +105,6 @@ The Crunchy PostgreSQL Operator makes it easy to get your own PostgreSQL-as-a-Se - Bring your own trusted certificate authority (CA) for use with the Operator API server - Override your PostgreSQL configuration for each cluster - [disaster-recovery]: https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/disaster-recovery/ [disaster-recovery-s3]: https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/disaster-recovery/#using-s3 [disaster-recovery-scheduling]: https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/disaster-recovery/#scheduling-backups @@ -119,19 +129,25 @@ The Crunchy PostgreSQL Operator makes it easy to get your own PostgreSQL-as-a-Se ## Deployment Requirements -The PostgreSQL Operator is validated for deployment on Kubernetes, OpenShift, and VMware Enterprise PKS clusters. Some form of storage is required, NFS, hostPath, and Storage Classes are currently supported. +PGO, the Postgres Operator, is validated for deployment on Kubernetes, +OpenShift, GKE, Anthos, AKS, EKS, and VMware Tanzu clusters. PGO is cloud native +and storage agnostic, working with a wide variety of storage classes, hostPath, +and NFS. -The PostgreSQL Operator includes various components that get deployed to your -Kubernetes cluster as shown in the following diagram and detailed -in the Design section of the documentation for the version you are running. +PGO includes various components that get deployed to your Kubernetes cluster as +shown in the following diagram and detailed in the Design section of the +documentation for the version you are running. ![Reference](https://access.crunchydata.com/documentation/postgres-operator/latest/Operator-Architecture.png) -The PostgreSQL Operator is developed and tested on CentOS and RHEL linux platforms but is known to run on other Linux variants. +PGO is developed and tested on CentOS and RHEL linux platforms but is known to +run on other Linux variants. ### Supported Platforms -The Crunchy PostgreSQL Operator maintains backwards compatibility to Kubernetes 1.11 and is tested is tested against the following Platforms: +PGO, the Postgres Operator, is Kubernetes-native and maintains backwards +compatibility to Kubernetes 1.11 and is tested is tested against the following +platforms: - Kubernetes 1.17+ - Openshift 4.4+ @@ -139,31 +155,33 @@ The Crunchy PostgreSQL Operator maintains backwards compatibility to Kubernetes - Google Kubernetes Engine (GKE), including Anthos - Amazon EKS - Microsoft AKS -- VMware Enterprise PKS 1.3+ +- VMware Tanzu -This list only includes the platforms that the PostgreSQL Operator is specifically tested on as part of the release process: the PostgreSQL Operator works on other Kubernetes distributions as well. +This list only includes the platforms that the Postgres Operator is specifically +tested on as part of the release process: PGO works on other Kubernetes +distributions as well. ### Storage -The Crunchy PostgreSQL Operator is tested with a variety of different types of Kubernetes storage and Storage Classes, including: - -- Google Compute Engine persistent volumes -- HostPath -- NFS -- Rook -- StorageOS - -and more. +PGO, the Postgres Operator, is tested with a variety of different types of +Kubernetes storage and Storage Classes, as well as hostPath and NFS. -We know there are a variety of different types of [Storage Classes](https://kubernetes.io/docs/concepts/storage/storage-classes/) available for Kubernetes and we do our best to test each one, but due to the breadth of this area we are unable to verify PostgreSQL Operator functionality in each one. With that said, the PostgreSQL Operator is designed to be storage class agnostic and has been demonstrated to work with additional Storage Classes. +We know there are a variety of different types of [Storage Classes](https://kubernetes.io/docs/concepts/storage/storage-classes/) +available for Kubernetes and we do our best to test each one, but due to the +breadth of this area we are unable to verify Postgres Operator functionality in +each one. With that said, the PostgreSQL Operator is designed to be storage +class agnostic and has been demonstrated to work with additional Storage +Classes. ## Installation -### PostgreSQL Operator Installation +### Postgres Operator (PGO) Installation -The PostgreSQL Operator provides a few different methods for installation based on your use case. +PGO provides a few different methods for installation methods to get up and +running with cloud native Postgres. -Based on your storage settings in your Kubernetes environment, you may be able to start as quickly as: +Based on your storage settings in your Kubernetes environment, you may be able +to start as quickly as: ```shell kubectl create namespace pgo @@ -182,7 +200,7 @@ Installations methods include: ### `pgo` Client Installation -If you have the PostgreSQL Operator installed in your environment, and are interested in installation of the client interface, please start here: +If you have the Postgres Operator installed in your environment, and are interested in installation of the client interface, please start here: - [pgo Client Install](https://access.crunchydata.com/documentation/postgres-operator/latest/installation/pgo-client/) @@ -229,20 +247,24 @@ Additional containers that are not directly integrated with the PostgreSQL Opera For more information about which versions of the PostgreSQL Operator include which components, please visit the [compatibility](https://access.crunchydata.com/documentation/postgres-operator/latest/configuration/compatibility/) section of the documentation. -## Using the PostgreSQL Operator +## Using the PostgreSQL Operator (PGO) -If you are new to the PostgreSQL Operator, you can follow along the [tutorial](https://access.crunchydata.com/documentation/postgres-operator/latest/tutorial/) to learn how to install the PostgreSQL Operator and how to use many of its features! +If you are new to PGO, you can follow along the [tutorial](https://access.crunchydata.com/documentation/postgres-operator/latest/tutorial/) +to learn how to install the PostgreSQL Operator and how to use many of its +features! - [PostgreSQL Operator Tutorial](https://access.crunchydata.com/documentation/postgres-operator/latest/tutorial/) -If you have the PostgreSQL and Client Interface installed in your environment and are interested in guidance on the use of the Crunchy PostgreSQL Operator, please start here: +If you have the PostgreSQL and client interface installed in your environment +and are interested in guidance on the use of the Crunchy PostgreSQL Operator, +please start here: -- [PostgreSQL Operator Documentation](https://access.crunchydata.com/documentation/postgres-operator/) +- [PostgreSQL Operator (PGO) Documentation](https://access.crunchydata.com/documentation/postgres-operator/) - [`pgo` Client User Guide](https://access.crunchydata.com/documentation/postgres-operator/latest/pgo-client/) ## Contributing to the Project -Want to contribute to the PostgreSQL Operator project? Great! We've put together +Want to contribute to the PGO Project? Great! We've put together as set of contributing guidelines that you can review here: - [Contributing Guidelines](CONTRIBUTING.md) @@ -275,7 +297,7 @@ For other information, please visit the [Support](https://access.crunchydata.com ## Documentation For additional information regarding design, configuration and operation of the -PostgreSQL Operator, pleases see the [Official Project Documentation][documentation]. +PostgreSQL Operator (PGO), please see the [Official Project Documentation][documentation]. If you are looking for the [nightly builds of the documentation](https://crunchydata.github.io/postgres-operator/latest/), you can view them at: @@ -289,7 +311,8 @@ Documentation for previous releases can be found at the [Crunchy Data Access Por ## Releases -When a PostgreSQL Operator general availability (GA) release occurs, the container images are distributed on the following platforms in order: +When a PGO general availability (GA) release occurs, the container images are +distributed on the following platforms in order: - [Crunchy Data Customer Portal](https://access.crunchydata.com/) - [Crunchy Data Developer Portal](https://www.crunchydata.com/developers) diff --git a/docs/config.toml b/docs/config.toml index 2bebb47add..af3d2d8607 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -2,7 +2,7 @@ baseURL= "" languageCode = "en-us" DefaultContentLanguage = "en" -title = "Crunchy PostgreSQL Operator Documentation" +title = "PGO: PostgreSQL Operator from Crunchy Data Documentation" theme = "crunchy-hugo-theme" pygmentsCodeFences = true pygmentsStyle = "monokailight" diff --git a/docs/content/_index.md b/docs/content/_index.md index 50fbc10943..e49b1a18e8 100644 --- a/docs/content/_index.md +++ b/docs/content/_index.md @@ -1,20 +1,30 @@ --- -title: "Crunchy PostgreSQL Operator" +title: "PGO: The Postgres Operator from Crunchy Data" date: draft: false --- -# Crunchy PostgreSQL Operator +# PGO: The Postgres Operator from Crunchy Data -## Run your own production-grade PostgreSQL-as-a-Service on Kubernetes! +## Run [Cloud Native PostgreSQL on Kubernetes](https://www.crunchydata.com/products/crunchy-postgresql-for-kubernetes/) with PGO: The [Postgres Operator](https://github.com/CrunchyData/postgres-operator) from [Crunchy Data](https://www.crunchydata.com/)! Latest Release: {{< param operatorVersion >}} -The [Crunchy PostgreSQL Operator](https://www.crunchydata.com/developers/download-postgres/containers/postgres-operator) automates and simplifies deploying and managing open source PostgreSQL clusters on Kubernetes and other Kubernetes-enabled Platforms by providing the essential features you need to keep your PostgreSQL clusters up and running, including: +[PGO](https://www.crunchydata.com/developers/download-postgres/containers/postgres-operator), +the [Postgres Operator](https://github.com/CrunchyData/postgres-operator) +developed by [Crunchy Data](https://crunchydata.com/) and included in +[Crunchy PostgreSQL for Kubernetes](https://www.crunchydata.com/products/crunchy-postgresql-for-kubernetes/), +automates and simplifies deploying and managing open source PostgreSQL clusters +on Kubernetes. -#### PostgreSQL Cluster [Provisioning]({{< relref "/architecture/provisioning.md" >}}) +Whether you need to get a simple Postgres cluster up and running, need to deploy +a high availability, fault tolerant cluster in production, or are running your +own database-as-a-service, the PostgreSQL Operator provides the essential +features you need to keep your cloud native Postgres clusters healthy, including: + +#### Postgres Cluster [Provisioning]({{< relref "/architecture/provisioning.md" >}}) [Create, Scale, & Delete PostgreSQL clusters with ease](/architecture/provisioning/), while fully customizing your Pods and PostgreSQL configuration! @@ -30,7 +40,7 @@ Backups and restores leverage the open source [pgBackRest](https://www.pgbackres #### TLS -Secure communication between your applications and data servers by [enabling TLS for your PostgreSQL servers](/pgo-client/common-tasks/#enable-tls), including the ability to enforce that all of your connections to use TLS. +Secure communication between your applications and data servers by [enabling TLS for your PostgreSQL servers](/pgo-client/common-tasks/#enable-tls), including the ability to enforce all of your connections to use TLS. #### [Monitoring]({{< relref "/architecture/monitoring.md" >}}) @@ -72,16 +82,22 @@ Choose the type of backup (full, incremental, differential) and [how frequently #### Multi-Namespace Support -You can control how the PostgreSQL Operator leverages [Kubernetes Namespaces](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/) with several different deployment models: +You can control how PGO, the Postgres Operator, leverages [Kubernetes Namespaces](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/) with several different deployment models: -- Deploy the PostgreSQL Operator and all PostgreSQL clusters to the same namespace -- Deploy the PostgreSQL Operator to one namespaces, and all PostgreSQL clusters to a different namespace -- Deploy the PostgreSQL Operator to one namespace, and have your PostgreSQL clusters managed acrossed multiple namespaces -- Dynamically add and remove namespaces managed by the PostgreSQL Operator using the `pgo create namespace` and `pgo delete namespace` commands +- Deploy PGO and all PostgreSQL clusters to the same namespace +- Deploy PGO to one namespaces, and all PostgreSQL clusters to a different +namespace +- Deploy PGO to one namespace, and have your PostgreSQL clusters managed across +multiple namespaces +- Dynamically add and remove namespaces managed by the PostgreSQL Operator using +the `pgo` client to run `pgo create namespace` and `pgo delete namespace` #### Full Customizability -The Crunchy PostgreSQL Operator makes it easy to get your own PostgreSQL-as-a-Service up and running on Kubernetes-enabled platforms, but we know that there are further customizations that you can make. As such, the Crunchy PostgreSQL Operator allows you to further customize your deployments, including: +The Postgres Operator (PGO) makes it easy to get Postgres up and running on +Kubernetes-enabled platforms, but we know that there are further customizations +that you can make. As such, PGO allows you to further customize your +deployments, including: - Selecting different storage classes for your primary, replica, and backup storage - Select your own container resources class for each PostgreSQL cluster deployment; differentiate between resources applied for primary and replica clusters! @@ -140,7 +156,9 @@ For more information about which versions of the PostgreSQL Operator include whi # Supported Platforms -The Crunchy PostgreSQL Operator maintains backwards compatibility to Kubernetes 1.11 and is tested is tested against the following Platforms: +PGO, the Postgres Operator, is Kubernetes-native and maintains backwards +compatibility to Kubernetes 1.11 and is tested is tested against the following +platforms: - Kubernetes 1.17+ - Openshift 4.4+ @@ -148,19 +166,20 @@ The Crunchy PostgreSQL Operator maintains backwards compatibility to Kubernetes - Google Kubernetes Engine (GKE), including Anthos - Amazon EKS - Microsoft AKS -- VMware Enterprise PKS 1.3+ - -This list only includes the platforms that the PostgreSQL Operator is specifically tested on as part of the release process: the PostgreSQL Operator works on other Kubernetes distributions as well. -## Storage +- VMware Tanzu -The Crunchy PostgreSQL Operator is tested with a variety of different types of Kubernetes storage and Storage Classes, including: +This list only includes the platforms that the Postgres Operator is specifically +tested on as part of the release process: PGO works on other Kubernetes +distributions as well. -- Rook -- StorageOS -- Google Compute Engine persistent volumes -- NFS -- HostPath +## Storage -and more. We have had reports of people using the PostgreSQL Operator with other [Storage Classes](https://kubernetes.io/docs/concepts/storage/storage-classes/) as well. +PGO, the Postgres Operator, is tested with a variety of different types of +Kubernetes storage and Storage Classes, as well as hostPath and NFS. -We know there are a variety of different types of [Storage Classes](https://kubernetes.io/docs/concepts/storage/storage-classes/) available for Kubernetes and we do our best to test each one, but due to the breadth of this area we are unable to verify PostgreSQL Operator functionality in each one. With that said, the PostgreSQL Operator is designed to be storage class agnostic and has been demonstrated to work with additional Storage Classes. Storage is a rapidly evolving field in Kubernetes and we will continue to adapt the PostgreSQL Operator to modern Kubernetes storage standards. +We know there are a variety of different types of [Storage Classes](https://kubernetes.io/docs/concepts/storage/storage-classes/) +available for Kubernetes and we do our best to test each one, but due to the +breadth of this area we are unable to verify Postgres Operator functionality in +each one. With that said, the PostgreSQL Operator is designed to be storage +class agnostic and has been demonstrated to work with additional Storage +Classes. From 2995db90dc405881f83a7bd689397ab4df801cd4 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 6 Apr 2021 11:12:05 -0400 Subject: [PATCH 213/276] Fix issue in metrics installer syntax There was a stray comma in the installer that set up the Prometheus deployment. --- .../roles/pgo-metrics/templates/prometheus-deployment.json.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/installers/metrics/ansible/roles/pgo-metrics/templates/prometheus-deployment.json.j2 b/installers/metrics/ansible/roles/pgo-metrics/templates/prometheus-deployment.json.j2 index 07a8b2e219..2618f07bed 100644 --- a/installers/metrics/ansible/roles/pgo-metrics/templates/prometheus-deployment.json.j2 +++ b/installers/metrics/ansible/roles/pgo-metrics/templates/prometheus-deployment.json.j2 @@ -31,7 +31,7 @@ {% if (prometheus_supplemental_groups | default('')) != '' %},{% endif -%} "fsGroup": 26, "runAsUser": 2, - "runAsNonRoot": true, + "runAsNonRoot": true {% endif %} }, "serviceAccountName": "prometheus-sa", From 504d716935ed4d980e7a9b4fc2c3961c21a7e1ef Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sat, 10 Apr 2021 14:47:51 -0400 Subject: [PATCH 214/276] Updates to installation instructions and other guidance A periodic pass through the installation instructions and other general updates to the documentation. --- README.md | 2 +- crunchy_logo.png | Bin 169205 -> 0 bytes docs/content/_index.md | 2 +- docs/content/installation/_index.md | 4 +- docs/content/installation/configuration.md | 4 +- .../metrics/other/ansible/_index.md | 8 +- docs/content/installation/other/_index.md | 2 +- .../installation/other/ansible/_index.md | 8 +- docs/content/installation/other/bash.md | 78 +++++++++--------- .../other/google-cloud-marketplace.md | 26 +++--- docs/content/installation/other/helm.md | 13 +-- .../installation/other/operator-hub.md | 10 +-- docs/content/installation/pgo-client.md | 14 ++-- .../content/installation/postgres-operator.md | 20 ++--- docs/content/installation/prerequisites.md | 14 ++-- docs/content/pgo-client/_index.md | 4 +- docs/content/quickstart/_index.md | 45 +++++----- docs/content/support/_index.md | 4 +- docs/content/tutorial/_index.md | 4 +- docs/content/tutorial/create-cluster.md | 4 + docs/content/tutorial/getting-started.md | 14 ++-- docs/static/crunchy-logo.jpg | Bin 100361 -> 0 bytes docs/static/pgo.png | Bin 0 -> 234696 bytes docs/static/pgo.svg | 1 + installers/ansible/README.md | 8 +- installers/gcp-marketplace/README.md | 4 +- installers/helm/Chart.yaml | 4 +- installers/helm/README.md | 16 ++-- installers/metrics/ansible/README.md | 10 +-- installers/metrics/helm/Chart.yaml | 2 +- installers/olm/README.md | 4 +- 31 files changed, 167 insertions(+), 162 deletions(-) delete mode 100644 crunchy_logo.png delete mode 100644 docs/static/crunchy-logo.jpg create mode 100644 docs/static/pgo.png create mode 100644 docs/static/pgo.svg diff --git a/README.md b/README.md index d0cc0c0b03..58416c0c2f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

PGO: The Postgres Operator from Crunchy Data

- Crunchy Data + PGO: The Postgres Operator from Crunchy Data

[![Go Report Card](https://goreportcard.com/badge/github.com/CrunchyData/postgres-operator)](https://goreportcard.com/report/github.com/CrunchyData/postgres-operator) diff --git a/crunchy_logo.png b/crunchy_logo.png deleted file mode 100644 index 2fbf3352c1dc55d5941d1b3faba7d687deaad4be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 169205 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%0D9u9fjOYB8#LD=NGMvsSxpVkmHQ{vJ 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Ìzq_*4>VdgPf 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 + PGO: The Postgres Operator from Crunchy Data ## Run [Cloud Native PostgreSQL on Kubernetes](https://www.crunchydata.com/products/crunchy-postgresql-for-kubernetes/) with PGO: The [Postgres Operator](https://github.com/CrunchyData/postgres-operator) from [Crunchy Data](https://www.crunchydata.com/)! diff --git a/docs/content/installation/_index.md b/docs/content/installation/_index.md index 909561ce46..036a080a4e 100644 --- a/docs/content/installation/_index.md +++ b/docs/content/installation/_index.md @@ -5,10 +5,10 @@ draft: false weight: 40 --- -There are several different ways to install and deploy the [PostgreSQL Operator](https://www.crunchydata.com/developers/download-postgres/containers/postgres-operator) +There are several different ways to install and deploy the [PGO, the Postgres Operator](https://www.crunchydata.com/developers/download-postgres/containers/postgres-operator) based upon your use case. -For the vast majority of use cases, we recommend using the [PostgreSQL Operator Installer]({{< relref "/installation/postgres-operator.md" >}}), +For the vast majority of use cases, we recommend using the [Postgres Operator Installer]({{< relref "/installation/postgres-operator.md" >}}), which uses the `pgo-deployer` container to set up all of the objects required to run the PostgreSQL Operator. diff --git a/docs/content/installation/configuration.md b/docs/content/installation/configuration.md index 52e69be444..5d31f2453f 100644 --- a/docs/content/installation/configuration.md +++ b/docs/content/installation/configuration.md @@ -5,9 +5,9 @@ draft: false weight: 40 --- -# PostgreSQL Operator Installer Configuration +# PGO Installer Configuration -When installing the PostgreSQL Operator you have many configuration options, these +When installing PGO, the Postgres Operator you have many configuration options, these options are listed in this section. ## General Configuration diff --git a/docs/content/installation/metrics/other/ansible/_index.md b/docs/content/installation/metrics/other/ansible/_index.md index cede0bb875..e48c6801dd 100644 --- a/docs/content/installation/metrics/other/ansible/_index.md +++ b/docs/content/installation/metrics/other/ansible/_index.md @@ -5,17 +5,17 @@ draft: false weight: 10 --- -# Crunchy Data PostgreSQL Operator Monitoring Playbooks +# PGO: Postgres Operator Monitoring Playbooks -The Crunchy Data PostgreSQL Operator Monitoring Playbooks contain [Ansible](https://www.ansible.com/) +PGO, the Postgres Operator from Crunchy Data, Monitoring Playbooks contain [Ansible](https://www.ansible.com/) roles for installing and managing the [Crunchy Data PostgreSQL Operator Monitoring infrastructure]({{< relref "/installation/other/ansible/installing-operator.md" >}}). ## Features The playbooks provided allow users to: -* install PostgreSQL Operator Monitoring on Kubernetes and OpenShift -* install PostgreSQL Operator from a Linux, Mac or Windows (Ubuntu subsystem) host +* install PGO Monitoring on Kubernetes and OpenShift +* install PGO from a Linux, Mac or Windows (Ubuntu subsystem) host * support a variety of deployment models ## Resources diff --git a/docs/content/installation/other/_index.md b/docs/content/installation/other/_index.md index 54722b3e61..1dc5fd84b8 100644 --- a/docs/content/installation/other/_index.md +++ b/docs/content/installation/other/_index.md @@ -6,6 +6,6 @@ weight: 50 --- Though the years, we have built up several other methods for installing the -PostgreSQL Operator. The next few sections provide some alternative ways of +PGO. The next few sections provide some alternative ways of deploying the PostgreSQL Operator. Some of these methods are deprecated and may be removed in a future release. diff --git a/docs/content/installation/other/ansible/_index.md b/docs/content/installation/other/ansible/_index.md index 0cd09a034d..69a647511e 100644 --- a/docs/content/installation/other/ansible/_index.md +++ b/docs/content/installation/other/ansible/_index.md @@ -5,17 +5,17 @@ draft: false weight: 100 --- -# Crunchy Data PostgreSQL Operator Playbooks +# PGO: Postgres Operator Playbooks -The Crunchy Data PostgreSQL Operator Playbooks contain [Ansible](https://www.ansible.com/) +PGO, the Postgres Operator from Crunchy Data, Playbooks contain [Ansible](https://www.ansible.com/) roles for installing and managing the [Crunchy Data PostgreSQL Operator]({{< relref "/installation/other/ansible/installing-operator.md" >}}). ## Features The playbooks provided allow users to: -* install PostgreSQL Operator on Kubernetes and OpenShift -* install PostgreSQL Operator from a Linux, Mac or Windows (Ubuntu subsystem) host +* install PGO on Kubernetes and OpenShift +* install PGO from a Linux, Mac or Windows (Ubuntu subsystem) host * generate TLS certificates required by the PostgreSQL Operator * support a variety of deployment models diff --git a/docs/content/installation/other/bash.md b/docs/content/installation/other/bash.md index be9963da7a..6d494182d0 100644 --- a/docs/content/installation/other/bash.md +++ b/docs/content/installation/other/bash.md @@ -5,18 +5,18 @@ draft: false weight: 100 --- -A full installation of the Operator includes the following steps: +A full installation of PGO includes the following steps: - - get the Operator project + - get the PGO project - configure your environment variables - - configure Operator templates + - configure PGO templates - create security resources - deploy the operator - - install pgo CLI (end user command tool) + - install `pgo` client (end user command tool) -Operator end-users are only required to install the pgo CLI client on their host and can skip the server-side installation steps. pgo CLI clients are provided for Linux, Mac, and Windows clients. +PGO end-users are only required to install the `pgo` client on their host and can skip the server-side installation steps. `pgo` clients are provided for Linux, Mac, and Windows clients. -The Operator can be deployed by multiple methods including: +PGO can be deployed by multiple methods including: * default installation * Ansible playbook installation @@ -25,7 +25,7 @@ The Operator can be deployed by multiple methods including: ## Default Installation - Get Project -The Operator project is hosted on GitHub. You can get a copy using `git clone`: +The PGO source code is made available on GitHub. You can get a copy using `git clone`: git clone -b v{{< param operatorVersion >}} https://github.com/CrunchyData/postgres-operator.git cd postgres-operator @@ -53,9 +53,9 @@ for Kubernetes events. This value is set as follows: This means namespaces called *pgouser1* and *pgouser2* will be created as part of the default installation. -{{% notice warning %}}In Kubernetes versions prior to 1.12 (including Openshift up through 3.11), there is a limitation that requires an extra step during installation for the operator to function properly with watched namespaces. This limitation does not exist when using Kubernetes 1.12+. When a list of namespaces are provided through the NAMESPACE environment variable, the setupnamespaces.sh script handles the limitation properly in both the bash and ansible installation. +{{% notice warning %}}In Kubernetes versions prior to 1.12 (including Openshift up through 3.11), there is a limitation that requires an extra step during installation for PGO to function properly with watched namespaces. This limitation does not exist when using Kubernetes 1.12+. When a list of namespaces are provided through the NAMESPACE environment variable, the setupnamespaces.sh script handles the limitation properly in both the bash and ansible installation. -However, if the user wishes to add a new watched namespace after installation, where the user would normally use pgo create namespace to add the new namespace, they should instead run the add-targeted-namespace.sh script or they may give themselves cluster-admin privileges instead of having to run setupnamespaces.sh script. Again, this is only required when running on a Kubernetes distribution whose version is below 1.12. In Kubernetes version 1.12+ the pgo create namespace command works as expected. +However, if the user wishes to add a new watched namespace after installation, where the user would normally use `pgo create namespace` to add the new namespace, they should instead run the add-targeted-namespace.sh script or they may give themselves cluster-admin privileges instead of having to run setupnamespaces.sh script. Again, this is only required when running on a Kubernetes distribution whose version is below 1.12. In Kubernetes version 1.12+ the pgo create namespace command works as expected. {{% /notice %}} @@ -77,13 +77,13 @@ Create the Operator namespaces using the Makefile target: The [Design](/design) section of this documentation talks further about the use of namespaces within the Operator. -## Default Installation - Configure Operator Templates +## Default Installation - Configure PGO Templates -Within the Operator [*PGO_CONF_DIR*](/developer-setup/) directory are several configuration files and templates used by the Operator to determine the various resources that it deploys on your Kubernetes cluster, specifically the PostgreSQL clusters it deploys. +Within PGO's [*PGO_CONF_DIR*](/developer-setup/) directory are several configuration files and templates used by PGO to determine the various resources that it deploys on your Kubernetes cluster, specifically the PostgreSQL clusters it deploys. -When you install the Operator you must make choices as to what kind of storage the Operator has to work with for example. Storage varies with each installation. As an installer, you would modify these configuration templates used by the Operator to customize its behavior. +When you install PGO you must make choices as to what kind of storage the Operator has to work with for example. Storage varies with each installation. As an installer, you would modify these configuration templates used by the Operator to customize its behavior. -**Note**: when you want to make changes to these Operator templates and configuration files after your initial installation, you will need to re-deploy the Operator in order for it to pick up any future configuration changes. +**Note**: when you want to make changes to these PGO templates and configuration files after your initial installation, you will need to re-deploy the Operator in order for it to pick up any future configuration changes. Here are some common examples of configuration changes most installers would make: @@ -104,12 +104,10 @@ Listed above are the *pgo.yaml* sections related to storage choices. *PrimarySt This sort of configuration allows for a PostgreSQL primary and replica to use different storage if you want. Other storage settings like *AccessMode*, *Size*, *StorageType*, and *StorageClass* further define the storage configuration. Currently, NFS, HostPath, and Storage Classes are supported in the configuration. -As part of the Operator installation, you will need to adjust these storage settings to suit your deployment requirements. For users wanting to try +As part of PGO installation, you will need to adjust these storage settings to suit your deployment requirements. For users wanting to try out the Operator on Google Kubernetes Engine you would make the following change to the storage configuration in pgo.yaml: - - For NFS Storage, it is assumed that there are sufficient Persistent Volumes (PV) created for the Operator to use when it creates Persistent Volume Claims (PVC). The creation of Persistent Volumes is something a Kubernetes cluster-admin user would typically provide before installing the Operator. There is an example script which can be used to create NFS Persistent Volumes located here: ./pv/create-nfs-pv.sh @@ -129,11 +127,11 @@ Operator. Other settings in *pgo.yaml* are described in the [pgo.yaml Configuration](/configuration/pgo-yaml-configuration) section of the documentation. -## Operator Security +## PGO Security -The Operator implements its own RBAC (Role Based Access Controls) for authenticating Operator users access to the Operator REST API. +PGO implements its own RBAC (Role Based Access Controls) for authenticating Operator users access to the PGO REST API. -A default admin user is created when the operator is deployed. Create a .pgouser in your home directory and insert the text from below: +A default admin user is created when PGO is deployed. Create a .pgouser in your home directory and insert the text from below: ``` admin:examplepassword @@ -151,7 +149,7 @@ To create a unique administrator user on deployment of the operator edit this fi $PGOROOT/deploy/install-bootstrap-creds.sh ``` -After installation users can create optional Operator users as follows: +After installation users can create optional PGO users as follows: ``` pgo create pgouser someuser --pgouser-namespaces="pgouser1,pgouser2" --pgouser-password=somepassword --pgouser-roles="somerole,someotherrole" @@ -160,13 +158,13 @@ pgo create pgouser someuser --pgouser-namespaces="pgouser1,pgouser2" --pgouser-p Note, you can also store the pgouser file in alternate locations, see the Security documentation for details. -Operator security is discussed in the Security section [Security](/security) of the documentation. +PGO security is further discussed in the section [Security]({{< relref "security/_index.md" >}}) section of the documentation. Adjust these settings to meet your local requirements. ## Default Installation - Create Kubernetes RBAC Controls -The Operator installation requires Kubernetes administrators to create Resources required by the Operator. These resources are only allowed to be created by a cluster-admin user. To install on Google Cloud, you will need a user +PGO installation requires Kubernetes administrators to create Resources required by PGO. These resources are only allowed to be created by a cluster-admin user. To install on Google Cloud, you will need a user account with cluster-admin privileges. If you own the GKE cluster you are installing on, you can add cluster-admin role to your account as follows: @@ -179,9 +177,9 @@ Tor create the Kubernetes RBAC used by the Operator, run the following as a clus make installrbac -This set of Resources is created a single time unless a new Operator +This set of Resources is created a single time unless a new PGO release requires these Resources to be recreated. Note that when you -run *make installrbac* the set of keys used by the Operator REST API and +run *make installrbac* the set of keys used by the PGO REST API and also the pgbackrest ssh keys are generated. Verify the Operator Custom Resource Definitions are created as follows: @@ -193,14 +191,15 @@ You should see the *pgclusters* CRD among the listed CRD resource types. See the Security documentation for a description of the various RBAC resources created and used by the Operator. -## Default Installation - Deploy the Operator +## Default Installation - Deploy PGO + At this point, you as a normal Kubernetes user should be able to deploy the Operator. To do this, run the following Makefile target: make deployoperator -This will cause any existing Operator to be removed first, then the configuration to be bundled into a ConfigMap, then the Operator Deployment to be created. +This will cause any existing PGO installation to be removed first, then the configuration to be bundled into a ConfigMap, then the Operator Deployment to be created. -This will create a postgres-operator Deployment and a postgres-operator Service.Operator administrators needing to make changes to the Operator +This will create a postgres-operator Deployment and a postgres-operator Service.Operator administrators needing to make changes to the PGO configuration would run this make target to pick up any changes to pgo.yaml, pgo users/roles, or the Operator templates. @@ -211,17 +210,20 @@ created using the default installation by running the following: make cleannamespaces -This will permanently delete each namespace the Operator installation +This will permanently delete each namespace the PGO installation created previously. -## pgo CLI Installation -Most users will work with the Operator using the *pgo* CLI tool. That tool is downloaded from the GitHub Releases page for the Operator (https://github.com/crunchydata/postgres-operator/releases). Crunchy Enterprise Customer can download the pgo binaries from https://access.crunchydata.com/ on the downloads page. +## `pgo` client Installation -The *pgo* client is provided in Mac, Windows, and Linux binary formats, +Most users will work with the Operator using the `pgo` client. That tool is downloaded from the GitHub Releases page for the Operator (https://github.com/crunchydata/postgres-operator/releases). Crunchy Data customers can download the `pgo` binaries from https://access.crunchydata.com/ on the downloads page. + +The `pgo` client is provided in Mac, Windows, and Linux binary formats, download the appropriate client to your local laptop or workstation to work with a remote Operator. +You can also use the `pgo-client` container. + {{% notice info %}} If TLS authentication was disabled during installation, please see the [TLS Configuration Page] ({{< relref "Configuration/tls.md" >}}) for additional configuration information. @@ -239,9 +241,9 @@ Prior to using *pgo*, users testing the Operator on a single host can specify th pgo version ``` -That URL address needs to be reachable from your local *pgo* client host. Your Kubernetes administrator will likely need to create a network route, ingress, or LoadBalancer service to expose the Operator REST API to applications outside of the Kubernetes cluster. Your Kubernetes administrator might also allow you to run the Kubernetes port-forward command, contact your administrator for details. +That URL address needs to be reachable from your local `pgo` client host. Your Kubernetes administrator will likely need to create a network route, ingress, or LoadBalancer service to expose the PGO REST API to applications outside of the Kubernetes cluster. Your Kubernetes administrator might also allow you to run the Kubernetes port-forward command, contact your administrator for details. -Next, the *pgo* client needs to reference the keys used to secure the Operator REST API: +Next, the `pgo` client needs to reference the keys used to secure the PGO REST API: ``` export PGO_CA_CERT=$PGOROOT/conf/postgres-operator/server.crt @@ -253,7 +255,7 @@ You can also specify these keys on the command line as follows: pgo version --pgo-ca-cert=$PGOROOT/conf/postgres-operator/server.crt --pgo-client-cert=$PGOROOT/conf/postgres-operator/server.crt --pgo-client-key=$PGOROOT/conf/postgres-operator/server.key -{{% notice tip %}} if you are running the Operator on Google Cloud, you would open up another terminal and run *kubectl port-forward ...* to forward the Operator pod port 8443 to your localhost where you can access the Operator API from your local workstation. +{{% notice tip %}} if you are running PGO on Google Cloud, you would open up another terminal and run *kubectl port-forward ...* to forward the Postgres Operator pod port 8443 to your localhost where you can access the PGO API from your local workstation. {{% /notice %}} At this point, you can test connectivity between your laptop or workstation and the Postgres Operator deployed on a Kubernetes cluster as follows: @@ -264,7 +266,7 @@ You should get back a valid response showing the client and server version numbe ## Verify the Installation -Now that you have deployed the Operator, you can verify that it is running correctly. +Now that you have deployed PGO, you can verify that it is running correctly. You should see a pod running that contains the Operator: @@ -275,10 +277,10 @@ You should see a pod running that contains the Operator: That pod should show 3 of 3 containers in *running* state and that the operator is installed into the *pgo* namespace. -The sample environment script, examples/env.sh, if used creates some bash functions that you can use to view the Operator logs. This is useful in case you find one of the Operator containers not in a running status. +The sample environment script, examples/env.sh, if used creates some bash functions that you can use to view the Postgres Operator logs. This is useful in case you find one of the PGO containers not in a running status. -Using the pgo CLI, you can verify the versions of the client and server match as follows: +Using the `pgo` client, you can verify the versions of the client and server match as follows: pgo version -This also tests connectivity between your pgo client host and the Operator server. +This also tests connectivity between your `pgo` client host and Postgres Operator container. diff --git a/docs/content/installation/other/google-cloud-marketplace.md b/docs/content/installation/other/google-cloud-marketplace.md index 64fa52a684..78885dc7af 100644 --- a/docs/content/installation/other/google-cloud-marketplace.md +++ b/docs/content/installation/other/google-cloud-marketplace.md @@ -5,7 +5,7 @@ draft: false weight: 200 --- -The PostgreSQL Operator is installed as part of [Crunchy PostgreSQL for GKE][gcm-listing] +PGO: the PostgreSQL Operator from Crunchy Data is installed as part of [Crunchy PostgreSQL for GKE][gcm-listing] that is available in the Google Cloud Marketplace. [gcm-listing]: https://console.cloud.google.com/marketplace/details/crunchydata/crunchy-postgresql-operator @@ -16,7 +16,6 @@ that is available in the Google Cloud Marketplace. Install [Crunchy PostgreSQL for GKE][gcm-listing] to a Google Kubernetes Engine cluster using Google Cloud Marketplace. - ## Step 2: Verify Installation Install `kubectl` using the `gcloud components` command of the [Google Cloud SDK][sdk-install] or @@ -25,7 +24,7 @@ by following the [Kubernetes documentation][kubectl-install]. [kubectl-install]: https://kubernetes.io/docs/tasks/tools/install-kubectl/ [sdk-install]: https://cloud.google.com/sdk/docs/install -Using the `gcloud` utility, ensure you are logged into the GKE cluster in which you installed the +Using the `gcloud` utility, ensure you are logged into the GKE cluster in which you installed PGO, the PostgreSQL Operator, and see that it is running in the namespace in which you installed it. For example, in the `pgo` namespace: @@ -44,7 +43,7 @@ pod/postgres-operator-56d6ccb97-tmz7m 4/4 Running 0 2m ``` -## Step 3: Install the PostgreSQL Operator User Keys +## Step 3: Install the PGO User Keys You will need to get TLS keys used to secure the Operator REST API. Again, in the `pgo` namespace: @@ -54,9 +53,9 @@ kubectl -n pgo get secret pgo.tls -o 'go-template={{ index .data "tls.key" | bas ``` -## Step 4: Setup PostgreSQL Operator User +## Step 4: Setup PGO User -The PostgreSQL Operator implements its own role-based access control (RBAC) system for authenticating and authorization PostgreSQL Operator users access to its REST API. A default PostgreSQL Operator user (aka a "pgouser") is created as part of the marketplace installation (these credentials are set during the marketplace deployment workflow). +PGO implements its own role-based access control (RBAC) system for authenticating and authorization PostgreSQL Operator users access to its REST API. A default PostgreSQL Operator user (aka a "pgouser") is created as part of the marketplace installation (these credentials are set during the marketplace deployment workflow). Create the pgouser file in `${HOME?}/.pgo//pgouser` and insert the user and password you created on deployment of the PostgreSQL Operator via GCP Marketplace. For example, if you set up a user with the username of `username` and a password of `hippo`: @@ -67,7 +66,7 @@ username:hippo ## Step 5: Setup Environment variables -The PostgreSQL Operator Client uses several environmental variables to make it easier for interfacing with the PostgreSQL Operator. +The `pgo` Client uses several environmental variables to make it easier for interfacing with the PGO, the Postgres Operator. Set the environmental variables to use the key / certificate pair that you pulled in Step 3 was deployed via the marketplace. Using the previous examples, You can set up environment variables with the following command: @@ -98,13 +97,13 @@ source ~/.bashrc **NOTE**: For macOS users, you must use `~/.bash_profile` instead of `~/.bashrc` -## Step 6: Install the PostgreSQL Operator Client `pgo` +## Step 6: Install the `pgo` Client -The [`pgo` client](/pgo-client/) provides a helpful command-line interface to perform key operations on a PostgreSQL Operator, such as creating a PostgreSQL cluster. +The [`pgo` client](/pgo-client/) provides a helpful command-line interface to perform key operations on a PGO Deployment, such as creating a PostgreSQL cluster. The `pgo` client can be downloaded from GitHub [Releases](https://github.com/crunchydata/postgres-operator/releases) (subscribers can download it from the [Crunchy Data Customer Portal](https://access.crunchydata.com)). -Note that the `pgo` client's version must match the version of the PostgreSQL Operator that you have deployed. For example, if you have deployed version {{< param operatorVersion >}} of the PostgreSQL Operator, you must use the `pgo` for {{< param operatorVersion >}}. +Note that the `pgo` client's version must match the deployed version of PGO. For example, if you have deployed version {{< param operatorVersion >}} of the PostgreSQL Operator, you must use the `pgo` for {{< param operatorVersion >}}. Once you have download the `pgo` client, change the permissions on the file to be executable if need be as shown below: @@ -112,9 +111,9 @@ Once you have download the `pgo` client, change the permissions on the file to b chmod +x pgo ``` -## Step 7: Connect to the PostgreSQL Operator +## Step 7: Connect to PGO -Finally, let's see if we can connect to the PostgreSQL Operator from the `pgo` client. In order to communicate with the PostgreSQL Operator API server, you will first need to set up a [port forward](https://kubernetes.io/docs/tasks/access-application-cluster/port-forward-access-application-cluster/) to your local environment. +Finally, let's see if we can connect to the Postgres Operator from the `pgo` client. In order to communicate with the PGO API server, you will first need to set up a [port forward](https://kubernetes.io/docs/tasks/access-application-cluster/port-forward-access-application-cluster/) to your local environment. In a new console window, run the following command to set up a port forward: @@ -137,7 +136,7 @@ pgo-apiserver version {{< param operatorVersion >}} ## Step 8: Create a Namespace -We are almost there! You can optionally add a namespace that can be managed by the PostgreSQL Operator to watch and to deploy a PostgreSQL cluster into. +We are almost there! You can optionally add a namespace that can be managed by PGO to watch and to deploy a PostgreSQL cluster into. ```shell pgo create namespace wateringhole @@ -194,4 +193,3 @@ cluster : hippo ``` The `pgo test` command provides you the basic information you need to connect to your PostgreSQL cluster from within your Kubernetes environment. For more detailed information, you can use `pgo show cluster -n wateringhole hippo`. - diff --git a/docs/content/installation/other/helm.md b/docs/content/installation/other/helm.md index b4bab8ff26..43da7fd553 100644 --- a/docs/content/installation/other/helm.md +++ b/docs/content/installation/other/helm.md @@ -5,12 +5,13 @@ draft: false weight: 100 --- -# The PostgreSQL Operator Helm Chart +# PGO: The Postgres Operator Helm Chart ## Overview -The PostgreSQL Operator comes with a container called `pgo-deployer` which -handles a variety of lifecycle actions for the PostgreSQL Operator, including: +PGO, the Postgres Operator from Crunchy Data, comes with a +container called `pgo-deployer` which handles a variety of +lifecycle actions for the PostgreSQL Operator, including: - Installation - Upgrading @@ -178,11 +179,11 @@ pgo-apiserver version {{< param operatorVersion >}} ## Upgrade and Uninstall -Once install has be completed using Helm, it will also be used to upgrade and +Once install has be completed using Helm, it will also be used to upgrade and uninstall your PostgreSQL Operator. {{% notice tip %}} -The `name` and `namespace` in the following sections should match the options +The `name` and `namespace` in the following sections should match the options provided at install. {{% /notice %}} @@ -208,7 +209,7 @@ helm uninstall -n ## Debugging -When the `pgo-deployer` job does not complete successfully, the resources that +When the `pgo-deployer` job does not complete successfully, the resources that are created and normally cleaned up by Helm will be left in your Kubernetes cluster. This will allow you to use the failed job and its logs to debug the issue. The following command will show the logs for the `pgo-deployer` diff --git a/docs/content/installation/other/operator-hub.md b/docs/content/installation/other/operator-hub.md index caebfb3a95..6495c53910 100644 --- a/docs/content/installation/other/operator-hub.md +++ b/docs/content/installation/other/operator-hub.md @@ -6,7 +6,7 @@ weight: 200 --- If your Kubernetes cluster is already running the [Operator Lifecycle Manager][OLM], -the PostgreSQL Operator can be installed as part of [Crunchy PostgreSQL for Kubernetes][hub-listing] +then PGO, the Postgres Operator from Crunchy Data, can be installed as part of [Crunchy PostgreSQL for Kubernetes][hub-listing] that is available in OperatorHub.io. [hub-listing]: https://operatorhub.io/operator/postgresql @@ -15,7 +15,7 @@ that is available in OperatorHub.io. ## Before You Begin -There are some optional Secrets you can add before installing the PostgreSQL Operator into your cluster. +There are some optional Secrets you can add before installing PGO into your cluster. ### Secrets (optional) @@ -29,7 +29,7 @@ kubectl -n "$PGO_OPERATOR_NAMESPACE" create secret generic pgo-backrest-repo-con ### Certificates (optional) -The PostgreSQL Operator has an API that uses TLS to communicate securely with clients. If you have +PGO has an API that uses TLS to communicate securely with clients. If you have a certificate bundle validated by your organization, you can install it now. If not, the API will automatically generate and use a self-signed certificate. @@ -71,7 +71,7 @@ YAML ## After You Install -Once the PostgreSQL Operator is installed in your Kubernetes cluster, you will need to do a few things +Once PGO is installed in your Kubernetes cluster, you will need to do a few things to use the [PostgreSQL Operator Client]({{< relref "/pgo-client/_index.md" >}}). Install the first set of client credentials and download the `pgo` binary and client certificates. @@ -81,7 +81,7 @@ PGO_CMD=kubectl ./deploy/install-bootstrap-creds.sh PGO_CMD=kubectl ./installers/kubectl/client-setup.sh ``` -The client needs to be able to reach the PostgreSQL Operator API from outside the Kubernetes cluster. +The client needs to be able to reach the PGO API from outside the Kubernetes cluster. Create an external service or forward a port locally. ``` diff --git a/docs/content/installation/pgo-client.md b/docs/content/installation/pgo-client.md index 69dae759e1..6c584168df 100644 --- a/docs/content/installation/pgo-client.md +++ b/docs/content/installation/pgo-client.md @@ -1,5 +1,5 @@ --- -title: "Install `pgo` Client" +title: "Install \"pgo\" Client" date: draft: false weight: 30 @@ -8,23 +8,22 @@ weight: 30 # Install the PostgreSQL Operator (`pgo`) Client The following will install and configure the `pgo` client on all systems. For the -purpose of these instructions it's assumed that the Crunchy PostgreSQL Operator -is already deployed. +purpose of these instructions it's assumed that PGO: the Postgres Operator from Crunchy +Data is already deployed. ## Prerequisites * For Kubernetes deployments: [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) configured to communicate with Kubernetes * For OpenShift deployments: [oc](https://docs.openshift.com/container-platform/3.11/cli_reference/get_started_cli.html) configured to communicate with OpenShift -To authenticate with the Crunchy PostgreSQL Operator API: +To authenticate with the PGO API: * Client CA Certificate * Client TLS Certificate * Client Key * `pgouser` file containing `:` -All of the requirements above should be obtained from an administrator who installed the Crunchy -PostgreSQL Operator. +All of the requirements above should be obtained from an administrator who installed PGO. ## Linux and macOS @@ -288,5 +287,4 @@ properly by simply running the following: pgo version ``` -If the above command outputs versions of both the client and API server, the Crunchy PostgreSQL -Operator client has been installed successfully. +If the above command outputs versions of both the client and API server, the `pgo` client has been installed successfully. diff --git a/docs/content/installation/postgres-operator.md b/docs/content/installation/postgres-operator.md index 0cbd542dd5..0a79ef9019 100644 --- a/docs/content/installation/postgres-operator.md +++ b/docs/content/installation/postgres-operator.md @@ -1,11 +1,11 @@ --- -title: Install the PostgreSQL Operator +title: Install PGO the Postgres Operator date: draft: false weight: 20 --- -# The PostgreSQL Operator Installer +# PGO: Postgres Operator Installer ## Quickstart @@ -23,8 +23,8 @@ the PostgreSQL Operator. ## Overview -The PostgreSQL Operator comes with a container called `pgo-deployer` which -handles a variety of lifecycle actions for the PostgreSQL Operator, including: +PGO comes with a container called `pgo-deployer` which +handles a variety of lifecycle actions for the Postgres Operator, including: - Installation - Upgrading @@ -52,7 +52,7 @@ environmental requirements. By default, the `pgo-deployer` uses a ServiceAccount called `pgo-deployer-sa` that has a ClusterRoleBinding (`pgo-deployer-crb`) with several ClusterRole permissions. This is required to create the [Custom Resource Definitions](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) -that power the PostgreSQL Operator. While the PostgreSQL Operator itself can be +that power PGO. While the Postgres Operator itself can be scoped to a specific namespace, you will need to have `cluster-admin` for the initial deployment, or privileges that allow you to install Custom Resource Definitions. The required list of privileges are available in the [postgres-operator.yml](https://raw.githubusercontent.com/CrunchyData/postgres-operator/v{{< param operatorVersion >}}/installers/kubectl/postgres-operator.yml) file: @@ -82,7 +82,7 @@ For example, to create the `pgo` namespace: kubectl create namespace pgo ``` -The PostgreSQL Operator has the ability to manage PostgreSQL clusters across +The Postgres Operator has the ability to manage PostgreSQL clusters across multiple Kubernetes [Namespaces](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/), including the ability to add and remove Namespaces that it watches. Doing so does require the PostgreSQL Operator to have elevated privileges, and as such, @@ -120,7 +120,7 @@ PostgreSQL Operator cannot create the RBAC itself. ## Configuration - `postgres-operator.yml` The `postgres-operator.yml` file contains all of the configuration parameters -for deploying the PostgreSQL Operator. The [example file](https://github.com/CrunchyData/postgres-operator/blob/v{{< param operatorVersion >}}/installers/kubectl/postgres-operator.yml) +for deploying PGO. The [example file](https://github.com/CrunchyData/postgres-operator/blob/v{{< param operatorVersion >}}/installers/kubectl/postgres-operator.yml) contains defaults that should work in most Kubernetes environments, but it may require some customization. @@ -138,7 +138,7 @@ set to `install`, `update`, and `uninstall`. ### Image Pull Secrets -If you are pulling the PostgreSQL Operator images from a private registry, you +If you are pulling PGO images from a private registry, you will need to setup an [imagePullSecret](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/) with access to the registry. The image pull secret will need to be added to the @@ -174,7 +174,7 @@ oc secrets link --for=pull --namespace=}}) for instructions on how to install the PostgreSQL Operator Monitoring infrastructure. diff --git a/docs/content/installation/prerequisites.md b/docs/content/installation/prerequisites.md index 2df54859d9..0a70756057 100644 --- a/docs/content/installation/prerequisites.md +++ b/docs/content/installation/prerequisites.md @@ -7,11 +7,11 @@ weight: 10 # Prerequisites -The following is required prior to installing PostgreSQL Operator. +The following is required prior to installing PGO. ## Environment -The PostgreSQL Operator is tested in the following environments: +PGO is tested in the following environments: * Kubernetes v1.13+ * Red Hat OpenShift v3.11+ @@ -22,7 +22,7 @@ The PostgreSQL Operator is tested in the following environments: #### IBM Cloud Pak Data -If you install the PostgreSQL Operator, which comes with Crunchy +If you install PGO, which comes with Crunchy PostgreSQL for Kubernetes, on IBM Cloud Pak Data, please note the following additional requirements: @@ -33,14 +33,14 @@ additional requirements: * Minimum Memory Requirements: 120MB * Minimum Storage Requirements: 5MB -**Note**: PostgreSQL clusters deployed by the PostgreSQL Operator with +**Note**: PostgreSQL clusters deployed by PGO with Crunchy PostgreSQL for Kubernetes are workload dependent. As such, users should allocate enough resources for their PostgreSQL clusters. ## Client Interfaces -The PostgreSQL Operator installer will install the [`pgo` client]({{< relref "/pgo-client/_index.md" >}}) interface -to help with using the PostgreSQL Operator. However, it is also recommend that +The Postgres Operator installer will install the [`pgo` client]({{< relref "/pgo-client/_index.md" >}}) interface +to help with using PGO. However, it is also recommend that you have access to [`kubectl`](https://kubernetes.io/docs/tasks/tools/install-kubectl/) or [`oc`](https://www.okd.io/download.html) and are able to communicate with the Kubernetes or OpenShift cluster that you are working with. @@ -63,7 +63,7 @@ access to these ports. ### Application Ports -The PostgreSQL Operator deploys different services to support a production +PGO deploys different services to support a production PostgreSQL environment. Below is a list of the applications and their default Service ports. diff --git a/docs/content/pgo-client/_index.md b/docs/content/pgo-client/_index.md index f5fb7c7d3f..bc33712e18 100644 --- a/docs/content/pgo-client/_index.md +++ b/docs/content/pgo-client/_index.md @@ -6,11 +6,11 @@ weight: 50 --- The PostgreSQL Operator Client, aka `pgo`, is the most convenient way to -interact with the PostgreSQL Operator. `pgo` provides many convenience methods +interact with the Postgres Operator. `pgo` provides many convenience methods for creating, managing, and deleting PostgreSQL clusters through a series of simple commands. The `pgo` client interfaces with the API that is provided by the PostgreSQL Operator and can leverage the RBAC and TLS systems that are -provided by the PostgreSQL Operator +provided by the PGO: PostgreSQL Operator. ![Architecture](/Operator-Architecture.png) diff --git a/docs/content/quickstart/_index.md b/docs/content/quickstart/_index.md index d7559ba092..a74f532e24 100644 --- a/docs/content/quickstart/_index.md +++ b/docs/content/quickstart/_index.md @@ -5,26 +5,27 @@ draft: false weight: 10 --- -# PostgreSQL Operator Quickstart +# PGO: PostgreSQL Operator Quickstart -Can't wait to try out the PostgreSQL Operator? Let us show you the quickest possible path to getting up and running. +Can't wait to try out PGO, the Postgres Operator from Crunchy Data? Let us show +you the quickest possible path to getting up and running. -There are two paths to quickly get you up and running with the PostgreSQL Operator: +There are two paths to quickly get you up and running with PGO: -- [Installation via the PostgreSQL Operator Installer](#postgresql-operator-installer) +- [Installation via the Postgres Operator Installer](#postgresql-operator-installer) - Installation via a Marketplace - Installation via [Operator Lifecycle Manager]({{< relref "/installation/other/operator-hub.md" >}}) - Installation via [Google Cloud Marketplace]({{< relref "/installation/other/google-cloud-marketplace.md" >}}) Marketplaces can help you get more quickly started in your environment as they provide a mostly automated process, but there are a few steps you will need to take to ensure you can fully utilize your PostgreSQL Operator environment. You can find out more information about how to get started with one of those installers in the [Installation]({{< relref "/installation/_index.md" >}}) section. -# PostgreSQL Operator Installer +# Postgres Operator Installer Below will guide you through the steps for installing and using the PostgreSQL Operator using an installer that works with Ansible. ## Installation -### Install the PostgreSQL Operator +### Install PGO: the PostgreSQL Operator On environments that have a [default storage class](https://kubernetes.io/docs/tasks/administer-cluster/change-default-storage-class/) set up (which is most modern Kubernetes environments), the below command should work: @@ -39,14 +40,14 @@ If your install is unsuccessful, you may need to modify your configuration. Plea ### Install the `pgo` Client -During or after the installation of the PostgreSQL Operator, download the `pgo` client set up script. This will help set up your local environment for using the PostgreSQL Operator: +During or after the installation of PGO: the Postgres Operator, download the `pgo` client set up script. This will help set up your local environment for using the Postgres Operator: ``` curl https://raw.githubusercontent.com/CrunchyData/postgres-operator/v{{< param operatorVersion >}}/installers/kubectl/client-setup.sh > client-setup.sh chmod +x client-setup.sh ``` -When the PostgreSQL Operator is done installing, run the client setup script: +When the Postgres Operator is done installing, run the client setup script: ``` ./client-setup.sh @@ -83,9 +84,9 @@ source ~/.bashrc ### Post-Installation Setup -Below are a few steps to check if the PostgreSQL Operator is up and running. +Below are a few steps to check if PGO: the Postgres Operator is up and running. -By default, the PostgreSQL Operator installs into a namespace called `pgo`. First, see that the Kubernetes Deployment of the Operator exists and is healthy: +By default, PGO installs into a namespace called `pgo`. First, see that the Kubernetes Deployment of PGO exists and is healthy: ``` kubectl -n pgo get deployments @@ -111,7 +112,7 @@ NAME READY STATUS RESTARTS AGE postgres-operator-56d6ccb97-tmz7m 4/4 Running 0 2m ``` -Finally, let's see if we can connect to the PostgreSQL Operator from the `pgo` command-line client. The Ansible installer installs the `pgo` command line client into your environment, along with the username/password file that allows you to access the PostgreSQL Operator. In order to communicate with the PostgreSQL Operator API server, you will first need to set up a [port forward](https://kubernetes.io/docs/tasks/access-application-cluster/port-forward-access-application-cluster/) to your local environment. +Finally, let's see if we can connect to the Postgres Operator from the `pgo` command-line client. The Ansible installer installs the `pgo` command line client into your environment, along with the username/password file that allows you to access the PostgreSQL Operator. In order to communicate with the PostgreSQL Operator API server, you will first need to set up a [port forward](https://kubernetes.io/docs/tasks/access-application-cluster/port-forward-access-application-cluster/) to your local environment. In a new console window, run the following command to set up a port forward: @@ -134,7 +135,7 @@ pgo-apiserver version {{< param operatorVersion >}} ## Create a PostgreSQL Cluster -The quickstart installation method creates a namespace called `pgo` where the PostgreSQL Operator manages PostgreSQL clusters. Try creating a PostgreSQL cluster called `hippo`: +The quickstart installation method creates a namespace called `pgo` where PGO, the Postgres Operator, manages PostgreSQL clusters. Try creating a PostgreSQL cluster called `hippo`: ``` pgo create cluster -n pgo hippo @@ -155,7 +156,7 @@ created Pgcluster hippo workflow id 1cd0d225-7cd4-4044-b269-aa7bedae219b ``` -This will create a PostgreSQL cluster named `hippo`. It may take a few moments for the cluster to be provisioned. You can see the status of this cluster using the [`pgo test`]({{< relref "pgo-client/reference/pgo_test.md" >}}) command: +This will create a Postgres cluster named `hippo`. It may take a few moments for the cluster to be provisioned. You can see the status of this cluster using the [`pgo test`]({{< relref "pgo-client/reference/pgo_test.md" >}}) command: ``` pgo test -n pgo hippo @@ -175,7 +176,7 @@ The `pgo test` command provides you the basic information you need to connect to ## Connect to a PostgreSQL Cluster -By default, the PostgreSQL Operator creates a database inside the cluster with the same name of the cluster, in this case, `hippo`. Below demonstrates how we can connect to `hippo`. +By default, PGO creates a database inside the cluster with the same name of the cluster, in this case, `hippo`. Below demonstrates how we can connect to `hippo`. ### How Users Work @@ -193,7 +194,7 @@ CLUSTER USERNAME PASSWORD EXPIRES STATUS ERROR hippo testuser datalake never ok ``` -To get the information about all PostgreSQL users that the PostgreSQL Operator is managing, you will need to use the `--show-system-accounts` flag: +To get the information about all PostgreSQL users that PGO is managing, you will need to use the `--show-system-accounts` flag: ``` pgo show user -n pgo hippo --show-system-accounts @@ -217,7 +218,7 @@ The `primaryuser` is the used for replication and [high availability]({{< relref Let's see how we can connect to `hippo` using [`psql`](https://www.postgresql.org/docs/current/app-psql.html), the command-line tool for accessing PostgreSQL. Ensure you have [installed the `psql` client](https://www.crunchydata.com/developers/download-postgres/binaries/postgresql12). -The PostgreSQL Operator creates a service with the same name as the cluster. See for yourself! Get a list of all of the Services available in the `pgo` namespace: +PGO, the Postgres Operator, creates a service with the same name as the cluster. See for yourself! Get a list of all of the Services available in the `pgo` namespace: ``` kubectl -n pgo get svc @@ -293,7 +294,7 @@ For more information, please see the section on [pgAdmin 4]({{< relref "architec Some Kubernetes environments may require you to customize the configuration for the PostgreSQL Operator installer. The below provides a guide on the common parameters that require modification, though this may vary based on your installation. For a full reference, please visit the [Installation]({{< relref "/installation/_index.md" >}}) section. -If you already attempted to install the PostgreSQL Operator and that failed, the easiest way to clean up that installation is to delete the [Namespace](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/) that you attempted to install the PostgreSQL Operator into. **Note: This deletes all of the other objects in the Namespace, so please be sure this is OK!** +If you already attempted to install PGO and that failed, the easiest way to clean up that installation is to delete the [Namespace](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/) that you attempted to install the Postgres Operator into. **Note: This deletes all of the other objects in the Namespace, so please be sure this is OK!** To delete the namespace, you can run the following command: @@ -301,15 +302,15 @@ To delete the namespace, you can run the following command: kubectl delete namespace pgo ``` -#### Get the PostgreSQL Operator Installer Manifest +#### Get the Postgres Operator Installer Manifest -You will need to download the PostgreSQL Operator Installer manifest to your environment, which you can do with the following command: +You will need to download the Postgres Operator Installer manifest to your environment, which you can do with the following command: ``` curl https://raw.githubusercontent.com/CrunchyData/postgres-operator/v{{< param operatorVersion >}}/installers/kubectl/postgres-operator.yml > postgres-operator.yml ``` -#### Configure the PostgreSQL Operator Installer +#### Configure the Postgres Operator Installer There are many [configuration parameters]({{< relref "/installation/configuration.md">}}) to help you fine tune your installation, but there are a few that you may want to change to get the PostgreSQL Operator to run in your environment. Open up the `postgres-operator.yml` file and edit a few variables. @@ -326,11 +327,11 @@ primary_storage: "nfsstorage" replica_storage: "nfsstorage" ``` -In OpenShift and CodeReady Containers, the PostgreSQL Operator will automatically set `disable_fsgroup` to `true` so that it will deploy PostgreSQL clusters correctly under the `restricted` Security Context Constraint (SCC). Though we recommend using `restricted`, if you are using the `anyuid` SCC, you will need to set `disable_fsgroup` to `false` in order to deploy the PostgreSQL Operator. +In OpenShift and CodeReady Containers, PGO will automatically set `disable_fsgroup` to `true` so that it will deploy PostgreSQL clusters correctly under the `restricted` Security Context Constraint (SCC). Though we recommend using `restricted`, if you are using the `anyuid` SCC, you will need to set `disable_fsgroup` to `false` in order to deploy the PostgreSQL Operator. For a full list of available storage types that can be used with this installation method, please review the [configuration parameters]({{< relref "/installation/configuration.md">}}). -When you are done editing the file, you can install the PostgreSQL Operator by running the following commands: +When you are done editing the file, you can install PGO by running the following commands: ``` kubectl create namespace pgo diff --git a/docs/content/support/_index.md b/docs/content/support/_index.md index 707b93bc29..0a2ca60346 100644 --- a/docs/content/support/_index.md +++ b/docs/content/support/_index.md @@ -5,9 +5,9 @@ draft: false weight: 110 --- -There are a few options available for community support of the [PostgreSQL Operator](https://github.com/CrunchyData/postgres-operator): +There are a few options available for community support of the [PGO: the Postgres Operator](https://github.com/CrunchyData/postgres-operator): -- **If you believe you have found a bug** or have a detailed feature request: please open [an issue on GitHub](https://github.com/CrunchyData/postgres-operator/issues/new/choose). The PostgreSQL Operator community and the Crunchy Data team behind the PostgreSQL Operator is generally active in responding to issues. +- **If you believe you have found a bug** or have a detailed feature request: please open [an issue on GitHub](https://github.com/CrunchyData/postgres-operator/issues/new/choose). The Postgres Operator community and the Crunchy Data team behind the PGO is generally active in responding to issues. - **For general questions or community support**: please join the [PostgreSQL Operator community mailing list](https://groups.google.com/a/crunchydata.com/forum/#!forum/postgres-operator/join) at [https://groups.google.com/a/crunchydata.com/forum/#!forum/postgres-operator/join](https://groups.google.com/a/crunchydata.com/forum/#!forum/postgres-operator/join), In all cases, please be sure to provide as many details as possible in regards to your issue, including: diff --git a/docs/content/tutorial/_index.md b/docs/content/tutorial/_index.md index 2919cc6a25..0babd127dc 100644 --- a/docs/content/tutorial/_index.md +++ b/docs/content/tutorial/_index.md @@ -4,10 +4,10 @@ draft: false weight: 15 --- -The PostgreSQL Operator provides functionality that lets you run your own database-as-a-service: from deploying PostgreSQL clusters with [high availability]({{< relref "architecture/high-availability/_index.md" >}}), to a [full stack monitoring]({{< relref "architecture/high-availability/_index.md" >}}) solution, essential [disaster recovery and backup tools]({{< relref "architecture/disaster-recovery.md" >}}), the ability to secure your cluster with TLS, and much more! +PGO, the Postgres Operator, provides functionality that lets you run your own database-as-a-service: from deploying PostgreSQL clusters with [high availability]({{< relref "architecture/high-availability/_index.md" >}}), to a [full stack monitoring]({{< relref "architecture/high-availability/_index.md" >}}) solution, essential [disaster recovery and backup tools]({{< relref "architecture/disaster-recovery.md" >}}), the ability to secure your cluster with TLS, and much more! What's more, you can manage your PostgreSQL clusters with the convenient [`pgo` client]({{< relref "pgo-client/_index.md" >}}) or by interfacing directly with the PostgreSQL Operator [custom resources]({{< relref "custom-resources/_index.md" >}}). -Given the robustness of the PostgreSQL Operator, we think it's helpful to break down the functionality in this step-by-step tutorial. The tutorial covers the essential functions the PostgreSQL Operator can perform and covers many common basic and advanced use cases. +Given the robustness of PGO, we think it's helpful to break down the functionality in this step-by-step tutorial. The tutorial covers the essential functions the Postgres Operator can perform and covers many common basic and advanced use cases. So what are you waiting for? Let's [get started]({{< relref "tutorial/getting-started.md" >}})! diff --git a/docs/content/tutorial/create-cluster.md b/docs/content/tutorial/create-cluster.md index 3012787e93..6db4090269 100644 --- a/docs/content/tutorial/create-cluster.md +++ b/docs/content/tutorial/create-cluster.md @@ -143,6 +143,10 @@ has successfully started. - The password for the `ccp_monitoring` user has changed. In this case you will need to update the Secret with the monitoring credentials. +## Custom Resources + +You may also be curious about how to perform the same actions directly with [custom resources]({{< relref "custom-resources/_index.md" >}}). If that is the case, we encourage to skip ahead to the [Custom Resources]({{< relref "custom-resources/_index.md" >}}) section of the documentation. + ## Next Steps Once your cluster is created, the next step is to [connect to your PostgreSQL cluster]({{< relref "tutorial/connect-cluster.md" >}}). You can also [learn how to customize your PostgreSQL cluster]({{< relref "tutorial/customize-cluster.md" >}})! diff --git a/docs/content/tutorial/getting-started.md b/docs/content/tutorial/getting-started.md index 8422487ed1..c02fd355a4 100644 --- a/docs/content/tutorial/getting-started.md +++ b/docs/content/tutorial/getting-started.md @@ -6,17 +6,17 @@ weight: 100 ## Installation -If you have not installed the PostgreSQL Operator yet, we recommend you take a look at our [quickstart]({{< relref "quickstart/_index.md" >}}) or the [installation]({{< relref "installation/_index.md" >}}) sections. +If you have not installed PGO, the Postgres Operator, yet, we recommend you take a look at our [quickstart]({{< relref "quickstart/_index.md" >}}) or the [installation]({{< relref "installation/_index.md" >}}) sections. ### Customizing an Installation -How to customize a PostgreSQL Operator installation is a lengthy topic. The details are covered in the [installation]({{< relref "installation/postgres-operator.md" >}}) section, as well as a list of all the [configuration variables]({{< relref "installation/configuration.md" >}}) available. +How to customize a PGO installation is a lengthy topic. The details are covered in the [installation]({{< relref "installation/postgres-operator.md" >}}) section, as well as a list of all the [configuration variables]({{< relref "installation/configuration.md" >}}) available. ## Setup the `pgo` Client -This tutorial will be using the [`pgo` client]({{< relref "pgo-client/_index.md" >}}) to interact with the PostgreSQL Operator. Please follow the instructions in the [quickstart]({{< relref "quickstart/_index.md" >}}) or the [installation]({{< relref "installation/pgo-client.md" >}}) sections for how to configure the `pgo` client. +This tutorial will be using the [`pgo` client]({{< relref "pgo-client/_index.md" >}}) to interact with the Postgres Operator. Please follow the instructions in the [quickstart]({{< relref "quickstart/_index.md" >}}) or the [installation]({{< relref "installation/pgo-client.md" >}}) sections for how to configure the `pgo` client. -The PostgreSQL Operator and `pgo` client are designed to work in a [multi-namespace deployment environment]({{< relref "architecture/namespace.md" >}}) and many `pgo` commands require that the namespace flag (`-n`) are passed into it. You can use the `PGO_NAMESPACE` environmental variable to set which namespace a `pgo` command can use. For example: +The Postgres Operator and `pgo` client are designed to work in a [multi-namespace deployment environment]({{< relref "architecture/namespace.md" >}}) and many `pgo` commands require that the namespace flag (`-n`) are passed into it. You can use the `PGO_NAMESPACE` environmental variable to set which namespace a `pgo` command can use. For example: ``` export PGO_NAMESPACE=pgo @@ -39,13 +39,13 @@ export PGO_NAMESPACE=pgo ## Next Steps -Before proceeding, please make sure that your `pgo` client setup can communicate with your PostgreSQL Operator. In a separate terminal window, set up a port forward to your PostgreSQL Operator: +Before proceeding, please make sure that your `pgo` client setup can communicate with your PGO Deployment. In a separate terminal window, set up a port forward to your PostgreSQL Operator: ``` kubectl port-forward -n pgo svc/postgres-operator 8443:8443 ``` -The [`pgo version`]({{< relref "pgo-client/reference/pgo_version.md" >}}) command is a great way to check connectivity with the PostgreSQL Operator, as it is a very simple, safe operation. Try it out: +The [`pgo version`]({{< relref "pgo-client/reference/pgo_version.md" >}}) command is a great way to check connectivity with the Postgres Operator, as it is a very simple, safe operation. Try it out: ``` pgo version @@ -72,4 +72,4 @@ which yields results similar to: pgo client version {{< param operatorVersion >}} ``` -Alright, we're now ready to start our journey with the PostgreSQL Operator! +Alright, we're now ready to start our journey with PGO! diff --git a/docs/static/crunchy-logo.jpg b/docs/static/crunchy-logo.jpg deleted file mode 100644 index 01f9c9b1a4555771126876f02adde2d155b93b9a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 100361 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%0D^7vAL#$8yS%RkpRNIxJ2#Z0GK6VT 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 nVRx*w^&2WZ&EbkI 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 literal 0 HcmV?d00001 diff --git a/docs/static/pgo.svg b/docs/static/pgo.svg new file mode 100644 index 0000000000..b017c9851d --- /dev/null +++ b/docs/static/pgo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/installers/ansible/README.md b/installers/ansible/README.md index 8755aea178..1a58057647 100644 --- a/installers/ansible/README.md +++ b/installers/ansible/README.md @@ -1,15 +1,15 @@ -# Crunchy Data PostgreSQL Operator Playbook +# PGO: Postgres Operator Playbook

- Crunchy Data + PGO: The Postgres Operator from Crunchy Data

Latest Release: 4.6.2 ## General -This repository contains Ansible Roles for deploying the Crunchy PostgreSQL Operator -for Kubernetes and OpenShift. +This repository contains Ansible Roles for deploying PGO: the Postgres Operator +from [Crunchy Data](https://www.crunchydata.com) for Kubernetes and OpenShift. See the [official documentation for more information](https://crunchydata.github.io/postgres-operator/stable/) on installing Crunchy PostgreSQL Operator. diff --git a/installers/gcp-marketplace/README.md b/installers/gcp-marketplace/README.md index 5c4fb65043..1a75980180 100644 --- a/installers/gcp-marketplace/README.md +++ b/installers/gcp-marketplace/README.md @@ -1,11 +1,11 @@ - This directory contains the files that are used to install [Crunchy PostgreSQL for GKE][gcp-details], -which uses the PostgreSQL Operator, from the Google Cloud Marketplace. +which uses PGO: the PostgreSQL Operator from [Crunchy Data][crunchy-data], from the Google Cloud Marketplace. The integration centers around a container [image](./Dockerfile) that contains an installation [schema](./schema.yaml) and an [Application][k8s-app] [manifest](./application.yaml). Consult the [technical requirements][gcp-k8s-requirements] when making changes. +[crunchy-data]: https://www.crunchydata.com [k8s-app]: https://github.com/kubernetes-sigs/application/ [gcp-k8s]: https://cloud.google.com/marketplace/docs/kubernetes-apps/ [gcp-k8s-requirements]: https://cloud.google.com/marketplace/docs/partners/kubernetes-solutions/create-app-package diff --git a/installers/helm/Chart.yaml b/installers/helm/Chart.yaml index bb973cf805..960093a387 100644 --- a/installers/helm/Chart.yaml +++ b/installers/helm/Chart.yaml @@ -1,11 +1,11 @@ apiVersion: v2 name: postgres-operator -description: Crunchy PostgreSQL Operator Helm chart for Kubernetes +description: PGO: The Postgres Operator from Crunchy Data Helm Chart for Kubernetes type: application version: 0.2.0 appVersion: 4.6.2 home: https://github.com/CrunchyData/postgres-operator -icon: https://github.com/CrunchyData/postgres-operator/raw/master/crunchy_logo.png +icon: https://github.com/CrunchyData/postgres-operator/raw/master/docs/static/pgo.svg keywords: - PostgreSQL - Operator diff --git a/installers/helm/README.md b/installers/helm/README.md index 6f06abd9bb..0dbe9ac5a3 100644 --- a/installers/helm/README.md +++ b/installers/helm/README.md @@ -1,10 +1,10 @@ -# Crunchy PostgreSQL Operator +# PGO: The Postgres Operator from Crunchy Data -This Helm chart installs the Crunchy PostgreSQL Operator by using its “pgo-deployer” -container. Helm will setup the ServiceAccount, RBAC, and ConfigMap needed to run -the container as a Kubernetes Job. Then a job will be created based on `helm` -`install`, `upgrade`, or `uninstall`. After the job has completed the RBAC will -be cleaned up. +This Helm chart installs PGO: the Postgres Operator from Crunchy Data by using +its “pgo-deployer” container. Helm will setup the ServiceAccount, RBAC, and +ConfigMap needed to run the container as a Kubernetes Job. Then a job will +be created based on `helm` `install`, `upgrade`, or `uninstall`. After the +job has completed the RBAC will be cleaned up. ## Prerequisites @@ -39,10 +39,10 @@ cd postgres-operator/installers/helm helm uninstall postgres-operator -n pgo ``` -## Configuration +## Configuration The following shows the configurable parameters that are relevant to the Helm -Chart. A full list of all Crunchy PostgreSQL Operator configuration options can +Chart. A full list of all PGO configuration options can be found in the [documentation](https://access.crunchydata.com/documentation/postgres-operator/latest/installation/configuration/). | Name | Default | Description | diff --git a/installers/metrics/ansible/README.md b/installers/metrics/ansible/README.md index ca5f797410..e809dfa55c 100644 --- a/installers/metrics/ansible/README.md +++ b/installers/metrics/ansible/README.md @@ -1,15 +1,15 @@ -# Crunchy Data PostgreSQL Operator Monitoring Playbook +# PGO: Postgres Operator Monitoring Playbook

- Crunchy Data + Crunchy Data

Latest Release: 4.6.2 ## General -This repository contains Ansible Roles for deploying the metrics stack for the -Crunchy PostgreSQL Operator. +This repository contains Ansible Roles for deploying the metrics stack for PGO, +the Postgres Operator from [Crunchy Data](https://www.crunchydata.com). -See the [official Crunchy PostgreSQL Operator documentation](https://access.crunchydata.com/documentation/postgres-operator/) +See the [PGO documentation](https://access.crunchydata.com/documentation/postgres-operator/) for more information. diff --git a/installers/metrics/helm/Chart.yaml b/installers/metrics/helm/Chart.yaml index 9f659af095..cb6e6532d9 100644 --- a/installers/metrics/helm/Chart.yaml +++ b/installers/metrics/helm/Chart.yaml @@ -5,4 +5,4 @@ type: application version: 0.2.0 appVersion: 4.6.2 home: https://github.com/CrunchyData/postgres-operator -icon: https://github.com/CrunchyData/postgres-operator/raw/master/crunchy_logo.png +icon: https://github.com/CrunchyData/postgres-operator/raw/master/docs/static/pgo.svg diff --git a/installers/olm/README.md b/installers/olm/README.md index 9cf336ddac..51a04fedef 100644 --- a/installers/olm/README.md +++ b/installers/olm/README.md @@ -1,11 +1,11 @@ - This directory contains the files that are used to install [Crunchy PostgreSQL for Kubernetes][hub-listing], -which uses the PostgreSQL Operator, using [Operator Lifecycle Manager][OLM]. +which includes PGO, the Postgres Operator from [Crunchy Data][crunchy-data], using [Operator Lifecycle Manager][OLM]. The integration centers around a [ClusterServiceVersion][olm-csv] [manifest](./postgresoperator.csv.yaml) that gets packaged for OperatorHub. Changes there are accepted only if they pass all the [scorecard][] tests. Consult the [technical requirements][hub-contrib] when making changes. +[crunchy-data]: https://www.crunchydata.com [hub-contrib]: https://github.com/operator-framework/community-operators/blob/master/docs/contributing.md [hub-listing]: https://operatorhub.io/operator/postgresql [olm-csv]: https://github.com/operator-framework/operator-lifecycle-manager/blob/master/doc/design/building-your-csv.md From 36daf909703ef27f98700841fc62bd9a8216e13d Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 12 Apr 2021 21:26:12 -0400 Subject: [PATCH 215/276] Update logo file This allows for the logo to render better when using GitHub's dark mode. --- docs/static/pgo.png | Bin 234696 -> 262650 bytes docs/static/pgo.svg | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/static/pgo.png b/docs/static/pgo.png index 08b03dd59e9f6690909643370237b9cc7a8ae565..9d38c8f85958df48e081f1abeaf6ddefb957d87d 100644 GIT binary patch delta 126895 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
$ON-f4wP*9|lo348Q@_sbS@mRX{nMpXGhy@{7)+ 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 delta 101900 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

_J3jm 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

&*Jca^0D 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 ME7mhpZCpeD1I1gC;Q#;t diff --git a/docs/static/pgo.svg b/docs/static/pgo.svg index b017c9851d..d72f9d7810 100644 --- a/docs/static/pgo.svg +++ b/docs/static/pgo.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From 45ca8dc09f54d3197fc11c3d78ce614b0a39e51b Mon Sep 17 00:00:00 2001 From: Mathieu Parent Date: Wed, 14 Apr 2021 15:13:34 +0200 Subject: [PATCH 216/276] Allow overrides in install-bootstrap-creds.sh Allows for for credential variables to be overridden during the bootstrapping process on certain install methods (i.e. for development purposes and OLM). These variables include: - `PGOADMIN_USERNAME` - `PGOADMIN_PASSWORD` - `PGOADMIN_ROLENAME` --- deploy/install-bootstrap-creds.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deploy/install-bootstrap-creds.sh b/deploy/install-bootstrap-creds.sh index e25253104c..6eb2ff13b2 100755 --- a/deploy/install-bootstrap-creds.sh +++ b/deploy/install-bootstrap-creds.sh @@ -17,9 +17,9 @@ set -eu # fill out these variables if you want to change the # default pgo bootstrap user and role -PGOADMIN_USERNAME=admin -PGOADMIN_PASSWORD=examplepassword -PGOADMIN_ROLENAME=pgoadmin +PGOADMIN_USERNAME="${PGOADMIN_USERNAME:-admin}" +PGOADMIN_PASSWORD="${PGOADMIN_PASSWORD:-examplepassword}" +PGOADMIN_ROLENAME="${PGOADMIN_ROLENAME:-pgoadmin}" PGOADMIN_PERMS="*" From 18f3f706bc864aebc870655893a8ea3962990d32 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Wed, 14 Apr 2021 22:20:38 -0400 Subject: [PATCH 217/276] Properly identify pgAdmin Service from `pgo test` This was previously showing up as a "primary", which it certainly isn't. Now it is properly identified as its own thing, i.e. pgAdmin. Issue: [ch11100] --- internal/apiserver/clusterservice/clusterimpl.go | 9 +++++++-- pkg/apiservermsgs/clustermsgs.go | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index c0fa4acef5..dc9542aff3 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -299,12 +299,15 @@ func getServices(cluster *crv1.Pgcluster, ns string) ([]msgs.ShowClusterService, for _, p := range services.Items { d := msgs.ShowClusterService{} d.Name = p.Name - if strings.Contains(p.Name, "-backrest-repo") { + if strings.HasSuffix(p.Name, "-backrest-repo") { d.BackrestRepo = true d.ClusterName = cluster.Name - } else if strings.Contains(p.Name, "-pgbouncer") { + } else if strings.HasSuffix(p.Name, "-pgbouncer") { d.Pgbouncer = true d.ClusterName = cluster.Name + } else if strings.HasSuffix(p.Name, "-pgadmin") { + d.PGAdmin = true + d.ClusterName = cluster.Name } d.ClusterIP = p.Spec.ClusterIP for _, port := range p.Spec.Ports { @@ -485,6 +488,8 @@ func TestCluster(name, selector, ns, pgouser string, allFlag bool) msgs.ClusterT endpoint.InstanceType = msgs.ClusterTestInstanceTypePrimary case (strings.HasSuffix(service.Name, "-"+msgs.PodTypeReplica) && strings.Count(service.Name, "-"+msgs.PodTypeReplica) == 1): endpoint.InstanceType = msgs.ClusterTestInstanceTypeReplica + case service.PGAdmin: + endpoint.InstanceType = msgs.ClusterTestInstanceTypePGAdmin case service.Pgbouncer: endpoint.InstanceType = msgs.ClusterTestInstanceTypePGBouncer case service.BackrestRepo: diff --git a/pkg/apiservermsgs/clustermsgs.go b/pkg/apiservermsgs/clustermsgs.go index 912cf371c5..726e511622 100644 --- a/pkg/apiservermsgs/clustermsgs.go +++ b/pkg/apiservermsgs/clustermsgs.go @@ -263,6 +263,7 @@ type ShowClusterService struct { ClusterName string Pgbouncer bool BackrestRepo bool + PGAdmin bool } const ( @@ -506,6 +507,7 @@ type ClusterTestRequest struct { const ( ClusterTestInstanceTypePrimary = "primary" ClusterTestInstanceTypeReplica = "replica" + ClusterTestInstanceTypePGAdmin = "pgadmin" ClusterTestInstanceTypePGBouncer = "pgbouncer" ClusterTestInstanceTypeBackups = "backups" ClusterTestInstanceTypeUnknown = "unknown" From f29fa32a8b7dd4c659f62a18fd325ba9744f20ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20R=C3=A1=C4=8Dek?= Date: Wed, 21 Apr 2021 04:10:56 +0200 Subject: [PATCH 218/276] Several updates to Helm charts This provides several updates to Helm charts to match commonly accepted conventions. It provide unique names for each of the jobs and wrap the description in quotes. Issue: #2390 --- installers/helm/Chart.yaml | 2 +- installers/helm/templates/postgres-operator-install.yaml | 2 +- installers/helm/templates/postgres-operator-uninstall.yaml | 2 +- installers/helm/templates/postgres-operator-upgrade.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/installers/helm/Chart.yaml b/installers/helm/Chart.yaml index 960093a387..3f05644dac 100644 --- a/installers/helm/Chart.yaml +++ b/installers/helm/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v2 name: postgres-operator -description: PGO: The Postgres Operator from Crunchy Data Helm Chart for Kubernetes +description: 'PGO: The Postgres Operator from Crunchy Data Helm Chart for Kubernetes' type: application version: 0.2.0 appVersion: 4.6.2 diff --git a/installers/helm/templates/postgres-operator-install.yaml b/installers/helm/templates/postgres-operator-install.yaml index 43b0604c3b..8196dfb2a9 100644 --- a/installers/helm/templates/postgres-operator-install.yaml +++ b/installers/helm/templates/postgres-operator-install.yaml @@ -3,7 +3,7 @@ apiVersion: batch/v1 kind: Job metadata: - name: pgo-deploy + name: pgo-deploy-install namespace: {{ .Release.Namespace }} labels: {{ include "postgres-operator.labels" . | indent 4 }} diff --git a/installers/helm/templates/postgres-operator-uninstall.yaml b/installers/helm/templates/postgres-operator-uninstall.yaml index 0b7553b0e7..945295836b 100644 --- a/installers/helm/templates/postgres-operator-uninstall.yaml +++ b/installers/helm/templates/postgres-operator-uninstall.yaml @@ -3,7 +3,7 @@ apiVersion: batch/v1 kind: Job metadata: - name: pgo-deploy + name: pgo-deploy-uninstall namespace: {{ .Release.Namespace }} labels: {{ include "postgres-operator.labels" . | indent 4 }} diff --git a/installers/helm/templates/postgres-operator-upgrade.yaml b/installers/helm/templates/postgres-operator-upgrade.yaml index 4ba8954b14..30a450c8d4 100644 --- a/installers/helm/templates/postgres-operator-upgrade.yaml +++ b/installers/helm/templates/postgres-operator-upgrade.yaml @@ -3,7 +3,7 @@ apiVersion: batch/v1 kind: Job metadata: - name: pgo-deploy + name: pgo-deploy-upgrade namespace: {{ .Release.Namespace }} labels: {{ include "postgres-operator.labels" . | indent 4 }} From faa07781a6a1b6788b477acfbc3f362d66b17e4b Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 20 Apr 2021 10:26:14 -0400 Subject: [PATCH 219/276] Updates to various project assets This provides updates to various asset files available within the PGO project. --- README.md | 4 +- docs/content/_index.md | 4 +- docs/static/logos/TRADEMARKS.md | 143 +++++++++++++++++++++++++++ docs/static/{ => logos}/pgo.png | Bin docs/static/{ => logos}/pgo.svg | 0 installers/ansible/README.md | 2 +- installers/helm/Chart.yaml | 2 +- installers/metrics/ansible/README.md | 2 +- installers/metrics/helm/Chart.yaml | 2 +- 9 files changed, 153 insertions(+), 6 deletions(-) create mode 100644 docs/static/logos/TRADEMARKS.md rename docs/static/{ => logos}/pgo.png (100%) rename docs/static/{ => logos}/pgo.svg (100%) diff --git a/README.md b/README.md index 58416c0c2f..4ecf709b77 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

PGO: The Postgres Operator from Crunchy Data

- PGO: The Postgres Operator from Crunchy Data + PGO: The Postgres Operator from Crunchy Data

[![Go Report Card](https://goreportcard.com/badge/github.com/CrunchyData/postgres-operator)](https://goreportcard.com/report/github.com/CrunchyData/postgres-operator) @@ -321,3 +321,5 @@ distributed on the following platforms in order: The image rollout can occur over the course of several days. To stay up-to-date on when releases are made available in the [Crunchy Data Developer Portal](https://www.crunchydata.com/developers), please sign up for the [Crunchy Data Developer Program Newsletter](https://www.crunchydata.com/developers/newsletter) + +The PGO Postgres Operator project source code is available subject to the [Apache 2.0 license](LICENSE.md) with the PGO logo and branding assets covered by [our trademark guidelines](docs/static/logos/TRADEMARKS.md). diff --git a/docs/content/_index.md b/docs/content/_index.md index 0b85247105..34edfd00b0 100644 --- a/docs/content/_index.md +++ b/docs/content/_index.md @@ -6,7 +6,7 @@ draft: false # PGO: The Postgres Operator from Crunchy Data - PGO: The Postgres Operator from Crunchy Data + PGO: The Postgres Operator from Crunchy Data ## Run [Cloud Native PostgreSQL on Kubernetes](https://www.crunchydata.com/products/crunchy-postgresql-for-kubernetes/) with PGO: The [Postgres Operator](https://github.com/CrunchyData/postgres-operator) from [Crunchy Data](https://www.crunchydata.com/)! @@ -183,3 +183,5 @@ breadth of this area we are unable to verify Postgres Operator functionality in each one. With that said, the PostgreSQL Operator is designed to be storage class agnostic and has been demonstrated to work with additional Storage Classes. + +The PGO Postgres Operator project source code is available subject to the [Apache 2.0 license](https://raw.githubusercontent.com/CrunchyData/postgres-operator/master/LICENSE.md) with the PGO logo and branding assets covered by [our trademark guidelines](/logos/TRADEMARKS.md). diff --git a/docs/static/logos/TRADEMARKS.md b/docs/static/logos/TRADEMARKS.md new file mode 100644 index 0000000000..e97d80757d --- /dev/null +++ b/docs/static/logos/TRADEMARKS.md @@ -0,0 +1,143 @@ +# PGO Trademark Guidelines + +## 1. Introduction + +This document - the "Policy" - outlines the policy of The PGO Project (the "Project") for the use of our trademarks. + +A trademark’s role is to assure consumers about the quality of the associated products or services. Because an open source license allows you to modify the copyrighted software, we cannot be sure your modified software will not mislead recipients if it is distributed under our trademarks. So, this Policy describes when you may or may not use our trademarks. + +In this Policy, we are not trying to limit the lawful use of our trademarks, but rather describe what we consider lawful use. Trademark law can be ambiguous, so we hope to clarify whether we will consider your use permitted or non-infringing. + +The following sections describe the trademarks this Policy covers, as well as trademark uses we permit. If you want to use our trademarks in ways this Policy doesn’t address, please see "Where to get further information" below for contact information. Any use that does not comply with this Policy, or for which we have not separately provided written permission, is not a use we have approved. + +## 2. We are committed to open source principles + +We want to encourage and facilitate community use of our trademarks in a way that ensures the trademarks are meaningful source and quality indicators for our software and the associated goods and services and continue to embody the high reputation of the software and its associated community. This Policy therefore balances our need to ensure our trademarks remain reliable quality indicators and our community members’ desire to be full Project participants. + +## 3. Trademarks subject to the Policy + +Our trademarks + +This Policy covers: + +### 3.1 Our word trademarks and service marks (the "Word Marks"): + +PGO + +### 3.2. Our logo (the "Logo"): + +PGO: The Postgres Operator from Crunchy Data + +### 3.3 And the unique visual styling of our website (the "Trade Dress"). + +This Policy encompasses all Project trademarks and service marks, whether Word Marks, Logos or Trade Dress, which we collectively call the “Marks." We might not have registered some Marks, but this Policy covers our Marks regardless. + +## 4. Universal considerations for all uses + +Whenever you use a Mark, you must not mislead anyone, either directly or by omission, about what they are getting and from whom. The law reflects this requirement in two major ways described below: it prohibits creating a "likelihood of confusion," but allows for "nominative use." + +For example, you cannot say you are distributing PGO software when you're distributing a modified version of it, because you likely would confuse people, since they are not getting the same features and functionality they would get if they downloaded the software from us. You also cannot use our Logo on your website to suggest your website is an official website or we endorse your website. + +You can, though, say, for example, you like the PGO software, you are a PGO community participant, you are providing unmodified PGO software, or you wrote a book describing how to use the PGO software. + +This fundamental requirement - that it is always clear to people what they are getting and from whom - is reflected throughout this Policy. It should guide you if you are unsure about how you are using the Marks. + +In addition: + +You may not use the Marks in association with software use or distribution if you don’t comply with the license for the software. + +You may not use or register the Marks as part of your own trademark, service mark, domain name, company name, trade name, product name or service name. + +Trademark law does not allow you to use names or trademarks that are too similar to ours. You therefore may not use an obvious Mark variant or phonetic equivalent, foreign language equivalent, takeoff, or abbreviation for a similar or compatible product or service. + +You will not acquire rights in the Marks, and any goodwill you generate using the Marks inures solely to our benefit. +## 5. Use for software + +See universal considerations for all uses, above, which also apply. + +### 5.1 Uses we consider non-infringing + +#### 5.1.1 Distributing unmodified source code or unmodified executable code we have compiled + +When you redistribute our unmodified software, you are not changing its quality or nature. Therefore, you may retain the Word Marks and Logos we have placed on the software, to identify your redistributed software whether you redistribute by optical media, memory stick or download of unmodified source and executable code. This only applies if you are redistributing official software from this Project that you have not changed. You can find the Logo files [here](/). + +#### 5.1.2 Distributing executable code you have compiled, or modified code + +You may use the Word Marks, but not the Logos, to describe the software’s origin, that is, that the code you are distributing is a modification of our software. You may say, for example, "this software is derived from the source code from the PGO Project." +Of course, you can place your own trademarks or logos on software to which you have made substantive modifications, because by modifying the software, you have become the origin of the modified software. + +#### 5.1.3 Statements about compatibility, interoperability or derivation + +You may use the Word Marks, but not the Logos, to describe the relationship between your software and ours. You should use Our Mark after a verb or preposition that describes that relationship. So, you may say, for example, "Bob's plug-in for PGO," but may not say "Bob's PGO plug-in." + +#### 5.1.4 Using trademarks to show community affiliation + +This section discusses using our Marks for application themes, skins and personas. We discuss using our Marks on websites below. +You may use the Word Marks and the Logos in themes, personas, or skins to show your Project support, provided the use is non-commercial and clearly decorative, as contrasted with a use that appears to be the branding for a website or application. + +### 5.2 Permitted uses + +#### 5.2.1 Distributing unmodified software + +You may use the Word Marks and Logos to distribute executable code if you make the code from official Project source code using the procedure for creating an executable found at [https://access.crunchydata.com/documentation/postgres-operator/latest/installation/](https://access.crunchydata.com/documentation/postgres-operator/latest/installation/). + +#### 5.3 Unpermitted uses we consider infringing + +We will likely consider it an infringement to use the Marks in software that combines our software with another software program. In addition to creating a single executable for both software programs, we would consider your software "combined" with ours if installing our software automatically installs yours. We would not consider your software "combined" with ours if it is on the same media but requires separate, independent action to install. + +## 6. Use for non-software goods and services + +See universal considerations for all uses, above, which also apply. + +### 6.1 Uses we consider non-infringing + +#### 6.1.1 Websites + +You may use the Word Marks and Logos on your webpage to show your Project support if: + +- Your own branding or naming is more prominent than any Project Marks; +- The Logos hyperlink to the Project website: [https://github.com/CrunchyData/postgres-operator](https://github.com/CrunchyData/postgres-operator); +- The site does not mislead customers into thinking your website, service, or product is our website, service, or product; and +- The site clearly states the Project does not affiliate with or endorse you. + +#### 6.1.2 Publishing and presenting + +You can use the Word Marks in book and article titles, and the Logo in illustrations within a document, if the use does not suggest we published, endorse, or agree with your work. + +#### 6.1.3 Events + +You can use the Logo to promote the software and Project at events. + +### 6.2 Permitted uses + +#### 6.2.1 Meetups and user groups + +You can use the Word Marks as part of your meetup or user group name if: + +- The group’s main focus is the software; +- Any software or services the group provides are without cost; +- The group does not make a profit; +- Any charge to attend meetings is only to cover the cost of the venue, food and drink. + +The universal considerations for all uses, above, still apply: specifically, you may not use or register the Marks as part of your own trademark, service mark, domain name, company name, trade name, product name or service name. + +### 6.3 Unpermitted uses we consider infringing + +We will likely consider it an infringement to use the Marks as part of a domain name or subdomain. +We also would likely consider it an infringement to use the Marks on for-sale promotional goods. + +## 7 General Information + +### 7.1 Trademark legends + +If you are using our Marks in a way described in the sections entitled "Permitted uses," put the following notice at the foot of the page where you have used the Mark (or, if in a book, on the credits page), on packaging or labeling, and on advertising or marketing materials: "The PGO Project is a trademark of Crunchy Data Solutions, Inc., used with permission." + +### 7.2 What to do when you see abuse + +If you are aware of a confusing use or misuse of the Marks, we would appreciate you bringing it to our attention. Please contact us at [trademarks@crunchydata.com](mailto:trademarks@crunchydata.com) so we can investigate it further. + +### 7.3 Where to get further information + +If you have questions, wish to speak about using our Marks in ways the Policy doesn’t address, or see abuse of our Marks, please send an email to [trademarks@crunchydata.com](mailto:trademarks@crunchydata.com). + +We based these guidelines on the Model Trademark Guidelines, available at [http://www.modeltrademarkguidelines.org](http://www.modeltrademarkguidelines.org), used under a Creative Commons Attribution 3.0 Unported license: [https://creativecommons.org/licenses/by/3.0/deed.en_US](https://creativecommons.org/licenses/by/3.0/deed.en_US). diff --git a/docs/static/pgo.png b/docs/static/logos/pgo.png similarity index 100% rename from docs/static/pgo.png rename to docs/static/logos/pgo.png diff --git a/docs/static/pgo.svg b/docs/static/logos/pgo.svg similarity index 100% rename from docs/static/pgo.svg rename to docs/static/logos/pgo.svg diff --git a/installers/ansible/README.md b/installers/ansible/README.md index 1a58057647..d57a482c5c 100644 --- a/installers/ansible/README.md +++ b/installers/ansible/README.md @@ -1,7 +1,7 @@ # PGO: Postgres Operator Playbook

- PGO: The Postgres Operator from Crunchy Data + PGO: The Postgres Operator from Crunchy Data

Latest Release: 4.6.2 diff --git a/installers/helm/Chart.yaml b/installers/helm/Chart.yaml index 3f05644dac..e1458b2beb 100644 --- a/installers/helm/Chart.yaml +++ b/installers/helm/Chart.yaml @@ -5,7 +5,7 @@ type: application version: 0.2.0 appVersion: 4.6.2 home: https://github.com/CrunchyData/postgres-operator -icon: https://github.com/CrunchyData/postgres-operator/raw/master/docs/static/pgo.svg +icon: https://github.com/CrunchyData/postgres-operator/raw/master/docs/static/logos/pgo.svg keywords: - PostgreSQL - Operator diff --git a/installers/metrics/ansible/README.md b/installers/metrics/ansible/README.md index e809dfa55c..f37baa0d2b 100644 --- a/installers/metrics/ansible/README.md +++ b/installers/metrics/ansible/README.md @@ -1,7 +1,7 @@ # PGO: Postgres Operator Monitoring Playbook

- Crunchy Data + Crunchy Data

Latest Release: 4.6.2 diff --git a/installers/metrics/helm/Chart.yaml b/installers/metrics/helm/Chart.yaml index cb6e6532d9..3b6c6d85ab 100644 --- a/installers/metrics/helm/Chart.yaml +++ b/installers/metrics/helm/Chart.yaml @@ -5,4 +5,4 @@ type: application version: 0.2.0 appVersion: 4.6.2 home: https://github.com/CrunchyData/postgres-operator -icon: https://github.com/CrunchyData/postgres-operator/raw/master/docs/static/pgo.svg +icon: https://github.com/CrunchyData/postgres-operator/raw/master/docs/static/logos/pgo.svg From ef1508d932a8ba369d00a61cd6fa2c862a21d895 Mon Sep 17 00:00:00 2001 From: Magnus Hagander Date: Wed, 21 Apr 2021 20:58:17 +0200 Subject: [PATCH 220/276] Fix typo Fixes incorrect spelling of "primary". --- .../architecture/high-availability/multi-cluster-kubernetes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/architecture/high-availability/multi-cluster-kubernetes.md b/docs/content/architecture/high-availability/multi-cluster-kubernetes.md index 7944056673..0ce2960a83 100644 --- a/docs/content/architecture/high-availability/multi-cluster-kubernetes.md +++ b/docs/content/architecture/high-availability/multi-cluster-kubernetes.md @@ -226,7 +226,7 @@ cluster : standby (crunchy-postgres-ha:{{< param centosBase >}}-{{< param postgr There comes a time where a standby cluster needs to be promoted to an active cluster. Promoting a standby cluster means that a PostgreSQL instance within -it will become a priary and start accepting both reads and writes. This has the +it will become a primary and start accepting both reads and writes. This has the net effect of pushing WAL (transaction archives) to the pgBackRest repository, so we need to take a few steps first to ensure we don't accidentally create a split-brain scenario. From 2dbbd9de11331e22c42adba4bc4e0c58b75ad44e Mon Sep 17 00:00:00 2001 From: andrewlecuyer <43458182+andrewlecuyer@users.noreply.github.com> Date: Fri, 23 Apr 2021 20:51:14 -0500 Subject: [PATCH 221/276] Installs Required Packages for pgo-scheduler When building the 'pgo-scheduler' image using ubi8-minimal, the 'findutils' and 'procps' packages are now installed. This ensures the 'find' utility is present as required by the scheduler's liveness probe, while also ensuring the 'pgrep' and 'pidof' utilities are present as required by the scheduler's 'start.sh' script. --- build/pgo-scheduler/Dockerfile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/build/pgo-scheduler/Dockerfile b/build/pgo-scheduler/Dockerfile index 49e3700e4e..ab1788a2ff 100644 --- a/build/pgo-scheduler/Dockerfile +++ b/build/pgo-scheduler/Dockerfile @@ -3,6 +3,7 @@ ARG BASEVER ARG PREFIX FROM ${PREFIX}/pgo-base:${BASEOS}-${BASEVER} +ARG BASEOS ARG PGVERSION ARG BACKREST_VERSION ARG PACKAGER @@ -30,6 +31,13 @@ RUN if [ "$DFSET" = "rhel" ] ; then \ && chown -R 2:2 /opt/cpm /pgo-config ; \ fi +RUN if [ "$BASEOS" = "ubi8" ]; then \ + ${PACKAGER} -y install \ + findutils \ + procps \ + && ${PACKAGER} -y clean all ; \ +fi + ADD bin/pgo-scheduler /opt/cpm/bin ADD installers/ansible/roles/pgo-operator/files/pgo-configs /default-pgo-config ADD conf/postgres-operator/pgo.yaml /default-pgo-config/pgo.yaml From d303300746ac2eeef2f29ae73efbd1cccc30d8c7 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 3 May 2021 13:12:35 -0400 Subject: [PATCH 222/276] Remove TLS cert generation for API server from installer The apiserver will reconcile its own TLS certificate -- it has been doing so for awhile. This unifies the method to ensure that only the apiserver will generate its certificate, unless the user explicitly provides one. Issue: [ch11380] --- .../other/ansible/installing-operator.md | 51 ++++++++++++------- .../roles/pgo-operator/tasks/certs.yml | 41 --------------- .../ansible/roles/pgo-operator/tasks/main.yml | 22 -------- 3 files changed, 33 insertions(+), 81 deletions(-) delete mode 100644 installers/ansible/roles/pgo-operator/tasks/certs.yml diff --git a/docs/content/installation/other/ansible/installing-operator.md b/docs/content/installation/other/ansible/installing-operator.md index 8cc82d1448..520732ce72 100644 --- a/docs/content/installation/other/ansible/installing-operator.md +++ b/docs/content/installation/other/ansible/installing-operator.md @@ -56,38 +56,53 @@ oc get deployments -n oc get pods -n ``` -## Configure Environment Variables - -After the Crunchy PostgreSQL Operator has successfully been installed we will need -to configure local environment variables before using the `pgo` client. +## Install the `pgo` Client {{% notice info %}} - If TLS authentication was disabled during installation, please see the [TLS Configuration Page] ({{< relref "Configuration/tls.md" >}}) for additional configuration information. - {{% / notice %}} -To configure the environment variables used by `pgo` run the following command: +During or after the installation of PGO: the Postgres Operator, download the `pgo` client set up script. This will help set up your local environment for using the Postgres Operator: -Note: `` should be replaced with the namespace the Crunchy PostgreSQL -Operator was deployed to. +``` +curl https://raw.githubusercontent.com/CrunchyData/postgres-operator/v{{< param operatorVersion >}}/installers/kubectl/client-setup.sh > client-setup.sh +chmod +x client-setup.sh +``` -```bash -cat <> ~/.bashrc -export PGOUSER="${HOME?}/.pgo//pgouser" -export PGO_CA_CERT="${HOME?}/.pgo//client.crt" -export PGO_CLIENT_CERT="${HOME?}/.pgo//client.crt" -export PGO_CLIENT_KEY="${HOME?}/.pgo//client.key" +When the Postgres Operator is done installing, run the client setup script: + +``` +./client-setup.sh +``` + +This will download the `pgo` client and provide instructions for how to easily use it in your environment. It will prompt you to add some environmental variables for you to set up in your session, which you can do with the following commands: + +``` +export PGOUSER="${HOME?}/.pgo/pgo/pgouser" +export PGO_CA_CERT="${HOME?}/.pgo/pgo/client.crt" +export PGO_CLIENT_CERT="${HOME?}/.pgo/pgo/client.crt" +export PGO_CLIENT_KEY="${HOME?}/.pgo/pgo/client.key" export PGO_APISERVER_URL='https://127.0.0.1:8443' -EOF +export PGO_NAMESPACE=pgo ``` -Apply those changes to the current session by running: +If you wish to permanently add these variables to your environment, you can run the following: + +``` +cat <> ~/.bashrc +export PGOUSER="${HOME?}/.pgo/pgo/pgouser" +export PGO_CA_CERT="${HOME?}/.pgo/pgo/client.crt" +export PGO_CLIENT_CERT="${HOME?}/.pgo/pgo/client.crt" +export PGO_CLIENT_KEY="${HOME?}/.pgo/pgo/client.key" +export PGO_APISERVER_URL='https://127.0.0.1:8443' +export PGO_NAMESPACE=pgo +EOF -```bash source ~/.bashrc ``` +**NOTE**: For macOS users, you must use `~/.bash_profile` instead of `~/.bashrc` + ## Verify `pgo` Connection In a separate terminal we need to setup a port forward to the Crunchy PostgreSQL diff --git a/installers/ansible/roles/pgo-operator/tasks/certs.yml b/installers/ansible/roles/pgo-operator/tasks/certs.yml deleted file mode 100644 index 83f2697df0..0000000000 --- a/installers/ansible/roles/pgo-operator/tasks/certs.yml +++ /dev/null @@ -1,41 +0,0 @@ ---- -- name: Ensure directory exists for local self-signed TLS certs. - file: - path: '{{ output_dir }}' - state: directory - tags: - - install - -- name: Generate Self-signed Certificate - command: openssl req \ - -x509 \ - -nodes \ - -newkey ec \ - -pkeyopt ec_paramgen_curve:prime256v1 \ - -pkeyopt ec_param_enc:named_curve \ - -sha384 \ - -days 1825 \ - -subj "/CN=*" \ - -keyout {{ output_dir }}/server.key \ - -out {{ output_dir }}/server.crt - args: - creates: "{{ output_dir }}/server.[kc][er][yt]" - tags: - - install - -- name: Ensure {{ pgo_keys_dir }} Directory Exists - file: - path: '{{ pgo_keys_dir }}' - state: directory - tags: - - install - -- name: Copy certificates to {{ pgo_keys_dir }} - command: "cp {{ output_dir }}/server.crt {{ pgo_keys_dir }}/client.crt" - tags: - - install - -- name: Copy keys to {{ pgo_keys_dir }} - command: "cp {{ output_dir }}/server.key {{ pgo_keys_dir }}/client.key" - tags: - - install diff --git a/installers/ansible/roles/pgo-operator/tasks/main.yml b/installers/ansible/roles/pgo-operator/tasks/main.yml index 92d14725ca..a19e3c7b12 100644 --- a/installers/ansible/roles/pgo-operator/tasks/main.yml +++ b/installers/ansible/roles/pgo-operator/tasks/main.yml @@ -44,10 +44,6 @@ tags: - uninstall -- include_tasks: certs.yml - tags: - - install - - name: Use kubectl or oc set_fact: kubectl_or_oc: "{{ openshift_oc_bin if openshift_oc_bin is defined else 'kubectl' }}" @@ -274,24 +270,6 @@ - (backrest_aws_s3_key | default('') != '') or (backrest_aws_s3_secret | default('') != '') - - name: PGO API Secret - tags: - - install - - update - block: - - name: Check PGO API Secret - shell: "{{ kubectl_or_oc }} get secret pgo.tls -n {{ pgo_operator_namespace }}" - register: pgo_tls_result - failed_when: false - - - name: Create PGO API Secret - command: | - {{ kubectl_or_oc }} create secret tls pgo.tls \ - --cert='{{ output_dir }}/server.crt' \ - --key='{{ output_dir }}/server.key' \ - -n {{ pgo_operator_namespace }} - when: pgo_tls_result.rc == 1 - - name: PGO ConfigMap tags: - install From d750918ed4511ce40de65a6419615d7d8ad9198f Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 3 May 2021 13:37:57 -0400 Subject: [PATCH 223/276] Remove explicit package installation in deployer As we are no longer calling openssl from the Ansible scripts, we do not need to install it explicitly. However, this is included in most, if not all, of the base images we use. --- build/pgo-deployer/Dockerfile | 4 ---- 1 file changed, 4 deletions(-) diff --git a/build/pgo-deployer/Dockerfile b/build/pgo-deployer/Dockerfile index fdfeb39104..240ec8a440 100644 --- a/build/pgo-deployer/Dockerfile +++ b/build/pgo-deployer/Dockerfile @@ -22,7 +22,6 @@ RUN if [ "$DFSET" = "centos" ] ; then \ ansible-${ANSIBLE_VERSION} \ which \ gettext \ - openssl \ && ${PACKAGER} -y clean all ; \ fi @@ -36,7 +35,6 @@ RUN if [ "$BASEOS" = "rhel7" ] ; then \ ansible-${ANSIBLE_VERSION} \ which \ gettext \ - openssl \ && ${PACKAGER} -y clean all --enablerepo='rhel-7-server-ose-4.4-rpms' ; \ fi @@ -50,7 +48,6 @@ RUN if [ "$BASEOS" = "ubi7" ] ; then \ ansible-${ANSIBLE_VERSION} \ which \ gettext \ - openssl \ && ${PACKAGER} -y clean all --enablerepo='rhel-7-server-ose-4.4-rpms' ; \ fi @@ -64,7 +61,6 @@ RUN if [ "$BASEOS" = "ubi8" ] ; then \ ansible-${ANSIBLE_VERSION} \ which \ gettext \ - openssl \ && ${PACKAGER} -y clean all --enablerepo='rhocp-4.5-for-rhel-8-x86_64-rpms' ; \ fi From 39b838edf8afd930469ec5df9410d6d37fd6cfb2 Mon Sep 17 00:00:00 2001 From: Magnus Hagander Date: Thu, 6 May 2021 16:00:37 +0200 Subject: [PATCH 224/276] Fix typos in casing of backrest parameters Different casing was used in different parts of the documentation for backrestResources and backrestLimits. This fixes it to be consistent with what's actually used. --- docs/content/custom-resources/_index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/custom-resources/_index.md b/docs/content/custom-resources/_index.md index f0c5c8a5d4..4e92297ed7 100644 --- a/docs/content/custom-resources/_index.md +++ b/docs/content/custom-resources/_index.md @@ -458,7 +458,7 @@ There following modification operations are supported on the #### Modify Resource Requests & Limits -Modifying the `resources`, `limits`, `backrestResources`, `backRestLimits`, +Modifying the `resources`, `limits`, `backrestResources`, `backrestLimits`, `pgBouncer.resources`, or `pgbouncer.limits` will cause the PostgreSQL Operator to apply the new values to the affected [Deployments](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/). @@ -729,7 +729,7 @@ make changes, as described below. | backrestConfig | `create` | Optional references to pgBackRest configuration files | | backrestLimits | `create`, `update` | Specify the container resource limits that the pgBackRest repository should use. Follows the [Kubernetes definitions of resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | | backrestRepoPath | `create` | Optional reference to the location of the pgBackRest repository. | -| BackrestResources | `create`, `update` | Specify the container resource requests that the pgBackRest repository should use. Follows the [Kubernetes definitions of resource requests](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | +| backrestResources | `create`, `update` | Specify the container resource requests that the pgBackRest repository should use. Follows the [Kubernetes definitions of resource requests](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container). | | backrestS3Bucket | `create` | An optional parameter that specifies a S3 bucket that pgBackRest should use. | | backrestS3Endpoint | `create` | An optional parameter that specifies the S3 endpoint pgBackRest should use. | | backrestS3Region | `create` | An optional parameter that specifies a cloud region that pgBackRest should use. | From 86c9df8cb5c9169f63af1cb9cd640041fbc36f83 Mon Sep 17 00:00:00 2001 From: Heath Lord Date: Fri, 23 Apr 2021 17:15:06 -0400 Subject: [PATCH 225/276] Dynamic collection of license files This allows for the dynamic inclusion of license files in the built container images. --- .gitignore | 2 + Makefile | 9 +- bin/license_aggregator.sh | 38 ++ build/pgo-base/Dockerfile | 1 - {redhat/licenses => licenses}/LICENSE.txt | 0 .../github.com/PuerkitoBio/purell/LICENSE | 12 - .../github.com/PuerkitoBio/urlesc/LICENSE | 27 -- .../github.com/cpuguy83/go-md2man/LICENSE.md | 21 - licenses/github.com/davecgh/go-spew/LICENSE | 15 - licenses/github.com/docker/spdystream/LICENSE | 191 --------- .../github.com/emicklei/go-restful/LICENSE | 22 - .../github.com/evanphx/json-patch/LICENSE | 25 -- licenses/github.com/fatih/color/LICENSE.md | 20 - licenses/github.com/ghodss/yaml/LICENSE | 50 --- .../github.com/go-openapi/jsonpointer/LICENSE | 202 --------- .../go-openapi/jsonreference/LICENSE | 202 --------- licenses/github.com/go-openapi/spec/LICENSE | 202 --------- licenses/github.com/go-openapi/swag/LICENSE | 202 --------- licenses/github.com/gogo/protobuf/LICENSE | 36 -- licenses/github.com/golang/glog/LICENSE | 191 --------- licenses/github.com/golang/protobuf/LICENSE | 28 -- licenses/github.com/google/btree/LICENSE | 202 --------- licenses/github.com/google/gofuzz/LICENSE | 202 --------- .../github.com/googleapis/gnostic/LICENSE | 203 --------- licenses/github.com/gorilla/context/LICENSE | 27 -- licenses/github.com/gorilla/mux/LICENSE | 27 -- .../gregjones/httpcache/LICENSE.txt | 7 - .../github.com/hashicorp/golang-lru/LICENSE | 362 ----------------- licenses/github.com/howeyc/gopass/LICENSE.txt | 15 - .../howeyc/gopass/OPENSOLARIS.LICENSE | 384 ------------------ licenses/github.com/imdario/mergo/LICENSE | 28 -- .../inconshreveable/mousetrap/LICENSE | 13 - licenses/github.com/json-iterator/go/LICENSE | 21 - licenses/github.com/juju/ratelimit/LICENSE | 191 --------- licenses/github.com/lib/pq/LICENSE.md | 8 - licenses/github.com/mailru/easyjson/LICENSE | 7 - .../github.com/mattn/go-colorable/LICENSE | 21 - licenses/github.com/mattn/go-isatty/LICENSE | 9 - .../github.com/modern-go/concurrent/LICENSE | 201 --------- .../github.com/modern-go/reflect2/LICENSE | 201 --------- licenses/github.com/petar/GoLLRB/LICENSE | 27 -- .../github.com/peterbourgon/diskv/LICENSE | 19 - .../russross/blackfriday/LICENSE.txt | 29 -- licenses/github.com/sirupsen/logrus/LICENSE | 21 - licenses/github.com/spf13/cobra/LICENSE.txt | 174 -------- licenses/github.com/spf13/pflag/LICENSE | 28 -- licenses/golang.org/x/crypto/LICENSE | 27 -- licenses/golang.org/x/net/LICENSE | 27 -- licenses/golang.org/x/sys/LICENSE | 27 -- licenses/golang.org/x/text/LICENSE | 27 -- licenses/gopkg.in/inf.v0/LICENSE | 28 -- licenses/gopkg.in/robfig/cron.v2/LICENSE | 21 - licenses/gopkg.in/yaml.v2/LICENSE | 201 --------- licenses/k8s.io/api/LICENSE | 202 --------- licenses/k8s.io/apimachinery/LICENSE | 202 --------- licenses/k8s.io/client-go/LICENSE | 202 --------- licenses/k8s.io/kube-openapi/LICENSE | 202 --------- 57 files changed, 46 insertions(+), 5043 deletions(-) create mode 100755 bin/license_aggregator.sh rename {redhat/licenses => licenses}/LICENSE.txt (100%) delete mode 100644 licenses/github.com/PuerkitoBio/purell/LICENSE delete mode 100644 licenses/github.com/PuerkitoBio/urlesc/LICENSE delete mode 100644 licenses/github.com/cpuguy83/go-md2man/LICENSE.md delete mode 100644 licenses/github.com/davecgh/go-spew/LICENSE delete mode 100644 licenses/github.com/docker/spdystream/LICENSE delete mode 100644 licenses/github.com/emicklei/go-restful/LICENSE delete mode 100644 licenses/github.com/evanphx/json-patch/LICENSE delete mode 100644 licenses/github.com/fatih/color/LICENSE.md delete mode 100644 licenses/github.com/ghodss/yaml/LICENSE delete mode 100644 licenses/github.com/go-openapi/jsonpointer/LICENSE delete mode 100644 licenses/github.com/go-openapi/jsonreference/LICENSE delete mode 100644 licenses/github.com/go-openapi/spec/LICENSE delete mode 100644 licenses/github.com/go-openapi/swag/LICENSE delete mode 100644 licenses/github.com/gogo/protobuf/LICENSE delete mode 100644 licenses/github.com/golang/glog/LICENSE delete mode 100644 licenses/github.com/golang/protobuf/LICENSE delete mode 100644 licenses/github.com/google/btree/LICENSE delete mode 100644 licenses/github.com/google/gofuzz/LICENSE delete mode 100644 licenses/github.com/googleapis/gnostic/LICENSE delete mode 100644 licenses/github.com/gorilla/context/LICENSE delete mode 100644 licenses/github.com/gorilla/mux/LICENSE delete mode 100644 licenses/github.com/gregjones/httpcache/LICENSE.txt delete mode 100644 licenses/github.com/hashicorp/golang-lru/LICENSE delete mode 100644 licenses/github.com/howeyc/gopass/LICENSE.txt delete mode 100644 licenses/github.com/howeyc/gopass/OPENSOLARIS.LICENSE delete mode 100644 licenses/github.com/imdario/mergo/LICENSE delete mode 100644 licenses/github.com/inconshreveable/mousetrap/LICENSE delete mode 100644 licenses/github.com/json-iterator/go/LICENSE delete mode 100644 licenses/github.com/juju/ratelimit/LICENSE delete mode 100644 licenses/github.com/lib/pq/LICENSE.md delete mode 100644 licenses/github.com/mailru/easyjson/LICENSE delete mode 100644 licenses/github.com/mattn/go-colorable/LICENSE delete mode 100644 licenses/github.com/mattn/go-isatty/LICENSE delete mode 100644 licenses/github.com/modern-go/concurrent/LICENSE delete mode 100644 licenses/github.com/modern-go/reflect2/LICENSE delete mode 100644 licenses/github.com/petar/GoLLRB/LICENSE delete mode 100644 licenses/github.com/peterbourgon/diskv/LICENSE delete mode 100644 licenses/github.com/russross/blackfriday/LICENSE.txt delete mode 100644 licenses/github.com/sirupsen/logrus/LICENSE delete mode 100644 licenses/github.com/spf13/cobra/LICENSE.txt delete mode 100644 licenses/github.com/spf13/pflag/LICENSE delete mode 100644 licenses/golang.org/x/crypto/LICENSE delete mode 100644 licenses/golang.org/x/net/LICENSE delete mode 100644 licenses/golang.org/x/sys/LICENSE delete mode 100644 licenses/golang.org/x/text/LICENSE delete mode 100644 licenses/gopkg.in/inf.v0/LICENSE delete mode 100644 licenses/gopkg.in/robfig/cron.v2/LICENSE delete mode 100644 licenses/gopkg.in/yaml.v2/LICENSE delete mode 100644 licenses/k8s.io/api/LICENSE delete mode 100644 licenses/k8s.io/apimachinery/LICENSE delete mode 100644 licenses/k8s.io/client-go/LICENSE delete mode 100644 licenses/k8s.io/kube-openapi/LICENSE diff --git a/.gitignore b/.gitignore index 210f4ef69a..6542af05bc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .DS_Store /vendor/ tools +licenses/* +!licenses/LICENSE.txt diff --git a/Makefile b/Makefile index ec25e785e9..071054389d 100644 --- a/Makefile +++ b/Makefile @@ -88,7 +88,7 @@ images = pgo-apiserver \ postgres-operator .PHONY: all installrbac setup setupnamespaces cleannamespaces \ - deployoperator cli-docs clean push pull release + deployoperator cli-docs clean push pull release license #======= Main functions ======= @@ -112,7 +112,7 @@ deployoperator: #======= Binary builds ======= -build: build-postgres-operator build-pgo-apiserver build-pgo-client build-pgo-rmdata build-pgo-scheduler +build: build-postgres-operator build-pgo-apiserver build-pgo-client build-pgo-rmdata build-pgo-scheduler license build-pgo-apiserver: $(GO_BUILD) -o bin/apiserver ./cmd/apiserver @@ -178,7 +178,7 @@ endif pgo-base: pgo-base-$(IMGBUILDER) -pgo-base-build: $(PGOROOT)/build/pgo-base/Dockerfile +pgo-base-build: build $(PGOROOT)/build/pgo-base/Dockerfile $(IMGCMDSTEM) \ -f $(PGOROOT)/build/pgo-base/Dockerfile \ -t $(PGO_IMAGE_PREFIX)/pgo-base:$(PGO_IMAGE_TAG) \ @@ -227,6 +227,9 @@ clean-deprecated: [ ! -n '$(GOBIN)' ] || rm -f $(GOBIN)/postgres-operator $(GOBIN)/apiserver $(GOBIN)/*pgo [ ! -d bin/postgres-operator ] || rm -r bin/postgres-operator +license: + ./bin/license_aggregator.sh + push: $(images:%=push-%) ; push-%: diff --git a/bin/license_aggregator.sh b/bin/license_aggregator.sh new file mode 100755 index 0000000000..044f8c016c --- /dev/null +++ b/bin/license_aggregator.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# Copyright 2021 Crunchy Data Solutions, Inc. +# 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. + +# Inputs / outputs +SCAN_DIR=${GOPATH}/pkg/mod +OUT_DIR=licenses + +# Fail on error +set -e + +# Clean up before we start our work +rm -rf $OUT_DIR/*/ + +# Get any file in the vendor directory with the word "license" in it. Note that we'll also keep its path +myLicenses=$(find $SCAN_DIR -type f | grep -i license) +for licensefile in $myLicenses +do + # make a new license directory matching the same vendor structure + licensedir=$(dirname $licensefile) + newlicensedir=$(echo $licensedir | sed "s:$SCAN_DIR:$OUT_DIR:" | sed 's:@[0-9a-zA-Z.\\-]*/:/:' | sed 's:@[0-9a-zA-Z.\\-]*::') + mkdir -p $newlicensedir + # And, copy over the license + cp -f $licensefile $newlicensedir +done + +sudo chmod -R 755 licenses diff --git a/build/pgo-base/Dockerfile b/build/pgo-base/Dockerfile index e9a80bea5c..d852c3855b 100644 --- a/build/pgo-base/Dockerfile +++ b/build/pgo-base/Dockerfile @@ -21,7 +21,6 @@ LABEL vendor="Crunchy Data" \ io.openshift.tags="postgresql,postgres,sql,nosql,crunchy" \ io.k8s.description="Trusted open source PostgreSQL-as-a-Service" -COPY redhat/licenses /licenses COPY redhat/atomic/help.1 /help.1 COPY redhat/atomic/help.md /help.md COPY licenses /licenses diff --git a/redhat/licenses/LICENSE.txt b/licenses/LICENSE.txt similarity index 100% rename from redhat/licenses/LICENSE.txt rename to licenses/LICENSE.txt diff --git a/licenses/github.com/PuerkitoBio/purell/LICENSE b/licenses/github.com/PuerkitoBio/purell/LICENSE deleted file mode 100644 index 4b9986dea7..0000000000 --- a/licenses/github.com/PuerkitoBio/purell/LICENSE +++ /dev/null @@ -1,12 +0,0 @@ -Copyright (c) 2012, Martin Angers -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -* Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licenses/github.com/PuerkitoBio/urlesc/LICENSE b/licenses/github.com/PuerkitoBio/urlesc/LICENSE deleted file mode 100644 index 7448756763..0000000000 --- a/licenses/github.com/PuerkitoBio/urlesc/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2012 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licenses/github.com/cpuguy83/go-md2man/LICENSE.md b/licenses/github.com/cpuguy83/go-md2man/LICENSE.md deleted file mode 100644 index 1cade6cef6..0000000000 --- a/licenses/github.com/cpuguy83/go-md2man/LICENSE.md +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Brian Goff - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/licenses/github.com/davecgh/go-spew/LICENSE b/licenses/github.com/davecgh/go-spew/LICENSE deleted file mode 100644 index bc52e96f2b..0000000000 --- a/licenses/github.com/davecgh/go-spew/LICENSE +++ /dev/null @@ -1,15 +0,0 @@ -ISC License - -Copyright (c) 2012-2016 Dave Collins - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/licenses/github.com/docker/spdystream/LICENSE b/licenses/github.com/docker/spdystream/LICENSE deleted file mode 100644 index 9e4bd4dbee..0000000000 --- a/licenses/github.com/docker/spdystream/LICENSE +++ /dev/null @@ -1,191 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - Copyright 2014-2015 Docker, Inc. - - 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. diff --git a/licenses/github.com/emicklei/go-restful/LICENSE b/licenses/github.com/emicklei/go-restful/LICENSE deleted file mode 100644 index ece7ec61ef..0000000000 --- a/licenses/github.com/emicklei/go-restful/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -Copyright (c) 2012,2013 Ernest Micklei - -MIT License - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/licenses/github.com/evanphx/json-patch/LICENSE b/licenses/github.com/evanphx/json-patch/LICENSE deleted file mode 100644 index 0eb9b72d84..0000000000 --- a/licenses/github.com/evanphx/json-patch/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2014, Evan Phoenix -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright notice - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. -* Neither the name of the Evan Phoenix nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licenses/github.com/fatih/color/LICENSE.md b/licenses/github.com/fatih/color/LICENSE.md deleted file mode 100644 index 25fdaf639d..0000000000 --- a/licenses/github.com/fatih/color/LICENSE.md +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2013 Fatih Arslan - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/licenses/github.com/ghodss/yaml/LICENSE b/licenses/github.com/ghodss/yaml/LICENSE deleted file mode 100644 index 7805d36de7..0000000000 --- a/licenses/github.com/ghodss/yaml/LICENSE +++ /dev/null @@ -1,50 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Sam Ghods - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - -Copyright (c) 2012 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licenses/github.com/go-openapi/jsonpointer/LICENSE b/licenses/github.com/go-openapi/jsonpointer/LICENSE deleted file mode 100644 index d645695673..0000000000 --- a/licenses/github.com/go-openapi/jsonpointer/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/licenses/github.com/go-openapi/jsonreference/LICENSE b/licenses/github.com/go-openapi/jsonreference/LICENSE deleted file mode 100644 index d645695673..0000000000 --- a/licenses/github.com/go-openapi/jsonreference/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/licenses/github.com/go-openapi/spec/LICENSE b/licenses/github.com/go-openapi/spec/LICENSE deleted file mode 100644 index d645695673..0000000000 --- a/licenses/github.com/go-openapi/spec/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/licenses/github.com/go-openapi/swag/LICENSE b/licenses/github.com/go-openapi/swag/LICENSE deleted file mode 100644 index d645695673..0000000000 --- a/licenses/github.com/go-openapi/swag/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/licenses/github.com/gogo/protobuf/LICENSE b/licenses/github.com/gogo/protobuf/LICENSE deleted file mode 100644 index 7be0cc7b62..0000000000 --- a/licenses/github.com/gogo/protobuf/LICENSE +++ /dev/null @@ -1,36 +0,0 @@ -Protocol Buffers for Go with Gadgets - -Copyright (c) 2013, The GoGo Authors. All rights reserved. -http://github.com/gogo/protobuf - -Go support for Protocol Buffers - Google's data interchange format - -Copyright 2010 The Go Authors. All rights reserved. -https://github.com/golang/protobuf - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - diff --git a/licenses/github.com/golang/glog/LICENSE b/licenses/github.com/golang/glog/LICENSE deleted file mode 100644 index 37ec93a14f..0000000000 --- a/licenses/github.com/golang/glog/LICENSE +++ /dev/null @@ -1,191 +0,0 @@ -Apache License -Version 2.0, January 2004 -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and -distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright -owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities -that control, are controlled by, or are under common control with that entity. -For the purposes of this definition, "control" means (i) the power, direct or -indirect, to cause the direction or management of such entity, whether by -contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the -outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising -permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, including -but not limited to software source code, documentation source, and configuration -files. - -"Object" form shall mean any form resulting from mechanical transformation or -translation of a Source form, including but not limited to compiled object code, -generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made -available under the License, as indicated by a copyright notice that is included -in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that -is based on (or derived from) the Work and for which the editorial revisions, -annotations, elaborations, or other modifications represent, as a whole, an -original work of authorship. For the purposes of this License, Derivative Works -shall not include works that remain separable from, or merely link (or bind by -name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version -of the Work and any modifications or additions to that Work or Derivative Works -thereof, that is intentionally submitted to Licensor for inclusion in the Work -by the copyright owner or by an individual or Legal Entity authorized to submit -on behalf of the copyright owner. For the purposes of this definition, -"submitted" means any form of electronic, verbal, or written communication sent -to the Licensor or its representatives, including but not limited to -communication on electronic mailing lists, source code control systems, and -issue tracking systems that are managed by, or on behalf of, the Licensor for -the purpose of discussing and improving the Work, but excluding communication -that is conspicuously marked or otherwise designated in writing by the copyright -owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf -of whom a Contribution has been received by Licensor and subsequently -incorporated within the Work. - -2. Grant of Copyright License. - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable copyright license to reproduce, prepare Derivative Works of, -publicly display, publicly perform, sublicense, and distribute the Work and such -Derivative Works in Source or Object form. - -3. Grant of Patent License. - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable (except as stated in this section) patent license to make, have -made, use, offer to sell, sell, import, and otherwise transfer the Work, where -such license applies only to those patent claims licensable by such Contributor -that are necessarily infringed by their Contribution(s) alone or by combination -of their Contribution(s) with the Work to which such Contribution(s) was -submitted. If You institute patent litigation against any entity (including a -cross-claim or counterclaim in a lawsuit) alleging that the Work or a -Contribution incorporated within the Work constitutes direct or contributory -patent infringement, then any patent licenses granted to You under this License -for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. - -You may reproduce and distribute copies of the Work or Derivative Works thereof -in any medium, with or without modifications, and in Source or Object form, -provided that You meet the following conditions: - -You must give any other recipients of the Work or Derivative Works a copy of -this License; and -You must cause any modified files to carry prominent notices stating that You -changed the files; and -You must retain, in the Source form of any Derivative Works that You distribute, -all copyright, patent, trademark, and attribution notices from the Source form -of the Work, excluding those notices that do not pertain to any part of the -Derivative Works; and -If the Work includes a "NOTICE" text file as part of its distribution, then any -Derivative Works that You distribute must include a readable copy of the -attribution notices contained within such NOTICE file, excluding those notices -that do not pertain to any part of the Derivative Works, in at least one of the -following places: within a NOTICE text file distributed as part of the -Derivative Works; within the Source form or documentation, if provided along -with the Derivative Works; or, within a display generated by the Derivative -Works, if and wherever such third-party notices normally appear. The contents of -the NOTICE file are for informational purposes only and do not modify the -License. You may add Your own attribution notices within Derivative Works that -You distribute, alongside or as an addendum to the NOTICE text from the Work, -provided that such additional attribution notices cannot be construed as -modifying the License. -You may add Your own copyright statement to Your modifications and may provide -additional or different license terms and conditions for use, reproduction, or -distribution of Your modifications, or for any such Derivative Works as a whole, -provided Your use, reproduction, and distribution of the Work otherwise complies -with the conditions stated in this License. - -5. Submission of Contributions. - -Unless You explicitly state otherwise, any Contribution intentionally submitted -for inclusion in the Work by You to the Licensor shall be under the terms and -conditions of this License, without any additional terms or conditions. -Notwithstanding the above, nothing herein shall supersede or modify the terms of -any separate license agreement you may have executed with Licensor regarding -such Contributions. - -6. Trademarks. - -This License does not grant permission to use the trade names, trademarks, -service marks, or product names of the Licensor, except as required for -reasonable and customary use in describing the origin of the Work and -reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. - -Unless required by applicable law or agreed to in writing, Licensor provides the -Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, -including, without limitation, any warranties or conditions of TITLE, -NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are -solely responsible for determining the appropriateness of using or -redistributing the Work and assume any risks associated with Your exercise of -permissions under this License. - -8. Limitation of Liability. - -In no event and under no legal theory, whether in tort (including negligence), -contract, or otherwise, unless required by applicable law (such as deliberate -and grossly negligent acts) or agreed to in writing, shall any Contributor be -liable to You for damages, including any direct, indirect, special, incidental, -or consequential damages of any character arising as a result of this License or -out of the use or inability to use the Work (including but not limited to -damages for loss of goodwill, work stoppage, computer failure or malfunction, or -any and all other commercial damages or losses), even if such Contributor has -been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. - -While redistributing the Work or Derivative Works thereof, You may choose to -offer, and charge a fee for, acceptance of support, warranty, indemnity, or -other liability obligations and/or rights consistent with this License. However, -in accepting such obligations, You may act only on Your own behalf and on Your -sole responsibility, not on behalf of any other Contributor, and only if You -agree to indemnify, defend, and hold each Contributor harmless for any liability -incurred by, or claims asserted against, such Contributor by reason of your -accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work - -To apply the Apache License to your work, attach the following boilerplate -notice, with the fields enclosed by brackets "[]" replaced with your own -identifying information. (Don't include the brackets!) The text should be -enclosed in the appropriate comment syntax for the file format. We also -recommend that a file or class name and description of purpose be included on -the same "printed page" as the copyright notice for easier identification within -third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/licenses/github.com/golang/protobuf/LICENSE b/licenses/github.com/golang/protobuf/LICENSE deleted file mode 100644 index 0f646931a4..0000000000 --- a/licenses/github.com/golang/protobuf/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -Copyright 2010 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - diff --git a/licenses/github.com/google/btree/LICENSE b/licenses/github.com/google/btree/LICENSE deleted file mode 100644 index d645695673..0000000000 --- a/licenses/github.com/google/btree/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/licenses/github.com/google/gofuzz/LICENSE b/licenses/github.com/google/gofuzz/LICENSE deleted file mode 100644 index d645695673..0000000000 --- a/licenses/github.com/google/gofuzz/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/licenses/github.com/googleapis/gnostic/LICENSE b/licenses/github.com/googleapis/gnostic/LICENSE deleted file mode 100644 index 6b0b1270ff..0000000000 --- a/licenses/github.com/googleapis/gnostic/LICENSE +++ /dev/null @@ -1,203 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - diff --git a/licenses/github.com/gorilla/context/LICENSE b/licenses/github.com/gorilla/context/LICENSE deleted file mode 100644 index 0e5fb87280..0000000000 --- a/licenses/github.com/gorilla/context/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2012 Rodrigo Moraes. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licenses/github.com/gorilla/mux/LICENSE b/licenses/github.com/gorilla/mux/LICENSE deleted file mode 100644 index 0e5fb87280..0000000000 --- a/licenses/github.com/gorilla/mux/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2012 Rodrigo Moraes. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licenses/github.com/gregjones/httpcache/LICENSE.txt b/licenses/github.com/gregjones/httpcache/LICENSE.txt deleted file mode 100644 index 81316beb0c..0000000000 --- a/licenses/github.com/gregjones/httpcache/LICENSE.txt +++ /dev/null @@ -1,7 +0,0 @@ -Copyright © 2012 Greg Jones (greg.jones@gmail.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/licenses/github.com/hashicorp/golang-lru/LICENSE b/licenses/github.com/hashicorp/golang-lru/LICENSE deleted file mode 100644 index be2cc4dfb6..0000000000 --- a/licenses/github.com/hashicorp/golang-lru/LICENSE +++ /dev/null @@ -1,362 +0,0 @@ -Mozilla Public License, version 2.0 - -1. Definitions - -1.1. "Contributor" - - means each individual or legal entity that creates, contributes to the - creation of, or owns Covered Software. - -1.2. "Contributor Version" - - means the combination of the Contributions of others (if any) used by a - Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" - - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - - means Source Code Form to which the initial Contributor has attached the - notice in Exhibit A, the Executable Form of such Source Code Form, and - Modifications of such Source Code Form, in each case including portions - thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - a. that the initial Contributor has attached the notice described in - Exhibit B to the Covered Software; or - - b. that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the terms of - a Secondary License. - -1.6. "Executable Form" - - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - - means a work that combines Covered Software with other material, in a - separate file or files, that is not Covered Software. - -1.8. "License" - - means this document. - -1.9. "Licensable" - - means having the right to grant, to the maximum extent possible, whether - at the time of the initial grant or subsequently, any and all of the - rights conveyed by this License. - -1.10. "Modifications" - - means any of the following: - - a. any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered Software; or - - b. any new file in Source Code Form that contains any Covered Software. - -1.11. "Patent Claims" of a Contributor - - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the License, - by the making, using, selling, offering for sale, having made, import, - or transfer of either its Contributions or its Contributor Version. - -1.12. "Secondary License" - - means either the GNU General Public License, Version 2.0, the GNU Lesser - General Public License, Version 2.1, the GNU Affero General Public - License, Version 3.0, or any later versions of those licenses. - -1.13. "Source Code Form" - - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that controls, is - controlled by, or is under common control with You. For purposes of this - definition, "control" means (a) the power, direct or indirect, to cause - the direction or management of such entity, whether by contract or - otherwise, or (b) ownership of more than fifty percent (50%) of the - outstanding shares or beneficial ownership of such entity. - - -2. License Grants and Conditions - -2.1. Grants - - Each Contributor hereby grants You a world-wide, royalty-free, - non-exclusive license: - - a. under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - - b. under Patent Claims of such Contributor to make, use, sell, offer for - sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - -2.2. Effective Date - - The licenses granted in Section 2.1 with respect to any Contribution - become effective for each Contribution on the date the Contributor first - distributes such Contribution. - -2.3. Limitations on Grant Scope - - The licenses granted in this Section 2 are the only rights granted under - this License. No additional rights or licenses will be implied from the - distribution or licensing of Covered Software under this License. - Notwithstanding Section 2.1(b) above, no patent license is granted by a - Contributor: - - a. for any code that a Contributor has removed from Covered Software; or - - b. for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - - c. under Patent Claims infringed by Covered Software in the absence of - its Contributions. - - This License does not grant any rights in the trademarks, service marks, - or logos of any Contributor (except as may be necessary to comply with - the notice requirements in Section 3.4). - -2.4. Subsequent Licenses - - No Contributor makes additional grants as a result of Your choice to - distribute the Covered Software under a subsequent version of this - License (see Section 10.2) or under the terms of a Secondary License (if - permitted under the terms of Section 3.3). - -2.5. Representation - - Each Contributor represents that the Contributor believes its - Contributions are its original creation(s) or it has sufficient rights to - grant the rights to its Contributions conveyed by this License. - -2.6. Fair Use - - This License is not intended to limit any rights You have under - applicable copyright doctrines of fair use, fair dealing, or other - equivalents. - -2.7. Conditions - - Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in - Section 2.1. - - -3. Responsibilities - -3.1. Distribution of Source Form - - All distribution of Covered Software in Source Code Form, including any - Modifications that You create or to which You contribute, must be under - the terms of this License. You must inform recipients that the Source - Code Form of the Covered Software is governed by the terms of this - License, and how they can obtain a copy of this License. You may not - attempt to alter or restrict the recipients' rights in the Source Code - Form. - -3.2. Distribution of Executable Form - - If You distribute Covered Software in Executable Form then: - - a. such Covered Software must also be made available in Source Code Form, - as described in Section 3.1, and You must inform recipients of the - Executable Form how they can obtain a copy of such Source Code Form by - reasonable means in a timely manner, at a charge no more than the cost - of distribution to the recipient; and - - b. You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter the - recipients' rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - - You may create and distribute a Larger Work under terms of Your choice, - provided that You also comply with the requirements of this License for - the Covered Software. If the Larger Work is a combination of Covered - Software with a work governed by one or more Secondary Licenses, and the - Covered Software is not Incompatible With Secondary Licenses, this - License permits You to additionally distribute such Covered Software - under the terms of such Secondary License(s), so that the recipient of - the Larger Work may, at their option, further distribute the Covered - Software under the terms of either this License or such Secondary - License(s). - -3.4. Notices - - You may not remove or alter the substance of any license notices - (including copyright notices, patent notices, disclaimers of warranty, or - limitations of liability) contained within the Source Code Form of the - Covered Software, except that You may alter any license notices to the - extent required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - - You may choose to offer, and to charge a fee for, warranty, support, - indemnity or liability obligations to one or more recipients of Covered - Software. However, You may do so only on Your own behalf, and not on - behalf of any Contributor. You must make it absolutely clear that any - such warranty, support, indemnity, or liability obligation is offered by - You alone, and You hereby agree to indemnify every Contributor for any - liability incurred by such Contributor as a result of warranty, support, - indemnity or liability terms You offer. You may include additional - disclaimers of warranty and limitations of liability specific to any - jurisdiction. - -4. Inability to Comply Due to Statute or Regulation - - If it is impossible for You to comply with any of the terms of this License - with respect to some or all of the Covered Software due to statute, - judicial order, or regulation then You must: (a) comply with the terms of - this License to the maximum extent possible; and (b) describe the - limitations and the code they affect. Such description must be placed in a - text file included with all distributions of the Covered Software under - this License. Except to the extent prohibited by statute or regulation, - such description must be sufficiently detailed for a recipient of ordinary - skill to be able to understand it. - -5. Termination - -5.1. The rights granted under this License will terminate automatically if You - fail to comply with any of its terms. However, if You become compliant, - then the rights granted under this License from a particular Contributor - are reinstated (a) provisionally, unless and until such Contributor - explicitly and finally terminates Your grants, and (b) on an ongoing - basis, if such Contributor fails to notify You of the non-compliance by - some reasonable means prior to 60 days after You have come back into - compliance. Moreover, Your grants from a particular Contributor are - reinstated on an ongoing basis if such Contributor notifies You of the - non-compliance by some reasonable means, this is the first time You have - received notice of non-compliance with this License from such - Contributor, and You become compliant prior to 30 days after Your receipt - of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent - infringement claim (excluding declaratory judgment actions, - counter-claims, and cross-claims) alleging that a Contributor Version - directly or indirectly infringes any patent, then the rights granted to - You by any and all Contributors for the Covered Software under Section - 2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user - license agreements (excluding distributors and resellers) which have been - validly granted by You or Your distributors under this License prior to - termination shall survive termination. - -6. Disclaimer of Warranty - - Covered Software is provided under this License on an "as is" basis, - without warranty of any kind, either expressed, implied, or statutory, - including, without limitation, warranties that the Covered Software is free - of defects, merchantable, fit for a particular purpose or non-infringing. - The entire risk as to the quality and performance of the Covered Software - is with You. Should any Covered Software prove defective in any respect, - You (not any Contributor) assume the cost of any necessary servicing, - repair, or correction. This disclaimer of warranty constitutes an essential - part of this License. No use of any Covered Software is authorized under - this License except under this disclaimer. - -7. Limitation of Liability - - Under no circumstances and under no legal theory, whether tort (including - negligence), contract, or otherwise, shall any Contributor, or anyone who - distributes Covered Software as permitted above, be liable to You for any - direct, indirect, special, incidental, or consequential damages of any - character including, without limitation, damages for lost profits, loss of - goodwill, work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses, even if such party shall have been - informed of the possibility of such damages. This limitation of liability - shall not apply to liability for death or personal injury resulting from - such party's negligence to the extent applicable law prohibits such - limitation. Some jurisdictions do not allow the exclusion or limitation of - incidental or consequential damages, so this exclusion and limitation may - not apply to You. - -8. Litigation - - Any litigation relating to this License may be brought only in the courts - of a jurisdiction where the defendant maintains its principal place of - business and such litigation shall be governed by laws of that - jurisdiction, without reference to its conflict-of-law provisions. Nothing - in this Section shall prevent a party's ability to bring cross-claims or - counter-claims. - -9. Miscellaneous - - This License represents the complete agreement concerning the subject - matter hereof. If any provision of this License is held to be - unenforceable, such provision shall be reformed only to the extent - necessary to make it enforceable. Any law or regulation which provides that - the language of a contract shall be construed against the drafter shall not - be used to construe this License against a Contributor. - - -10. Versions of the License - -10.1. New Versions - - Mozilla Foundation is the license steward. Except as provided in Section - 10.3, no one other than the license steward has the right to modify or - publish new versions of this License. Each version will be given a - distinguishing version number. - -10.2. Effect of New Versions - - You may distribute the Covered Software under the terms of the version - of the License under which You originally received the Covered Software, - or under the terms of any subsequent version published by the license - steward. - -10.3. Modified Versions - - If you create software not governed by this License, and you want to - create a new license for such software, you may create and use a - modified version of this License if you rename the license and remove - any references to the name of the license steward (except to note that - such modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary - Licenses If You choose to distribute Source Code Form that is - Incompatible With Secondary Licenses under the terms of this version of - the License, the notice described in Exhibit B of this License must be - attached. - -Exhibit A - Source Code Form License Notice - - This Source Code Form is subject to the - terms of the Mozilla Public License, v. - 2.0. If a copy of the MPL was not - distributed with this file, You can - obtain one at - http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular file, -then You may include the notice in a location (such as a LICENSE file in a -relevant directory) where a recipient would be likely to look for such a -notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - "Incompatible With Secondary Licenses" Notice - - This Source Code Form is "Incompatible - With Secondary Licenses", as defined by - the Mozilla Public License, v. 2.0. diff --git a/licenses/github.com/howeyc/gopass/LICENSE.txt b/licenses/github.com/howeyc/gopass/LICENSE.txt deleted file mode 100644 index 14f74708a4..0000000000 --- a/licenses/github.com/howeyc/gopass/LICENSE.txt +++ /dev/null @@ -1,15 +0,0 @@ -ISC License - -Copyright (c) 2012 Chris Howey - -Permission to use, copy, modify, and distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/licenses/github.com/howeyc/gopass/OPENSOLARIS.LICENSE b/licenses/github.com/howeyc/gopass/OPENSOLARIS.LICENSE deleted file mode 100644 index da23621dc8..0000000000 --- a/licenses/github.com/howeyc/gopass/OPENSOLARIS.LICENSE +++ /dev/null @@ -1,384 +0,0 @@ -Unless otherwise noted, all files in this distribution are released -under the Common Development and Distribution License (CDDL). -Exceptions are noted within the associated source files. - --------------------------------------------------------------------- - - -COMMON DEVELOPMENT AND DISTRIBUTION LICENSE Version 1.0 - -1. Definitions. - - 1.1. "Contributor" means each individual or entity that creates - or contributes to the creation of Modifications. - - 1.2. "Contributor Version" means the combination of the Original - Software, prior Modifications used by a Contributor (if any), - and the Modifications made by that particular Contributor. - - 1.3. "Covered Software" means (a) the Original Software, or (b) - Modifications, or (c) the combination of files containing - Original Software with files containing Modifications, in - each case including portions thereof. - - 1.4. "Executable" means the Covered Software in any form other - than Source Code. - - 1.5. "Initial Developer" means the individual or entity that first - makes Original Software available under this License. - - 1.6. "Larger Work" means a work which combines Covered Software or - portions thereof with code not governed by the terms of this - License. - - 1.7. "License" means this document. - - 1.8. "Licensable" means having the right to grant, to the maximum - extent possible, whether at the time of the initial grant or - subsequently acquired, any and all of the rights conveyed - herein. - - 1.9. "Modifications" means the Source Code and Executable form of - any of the following: - - A. Any file that results from an addition to, deletion from or - modification of the contents of a file containing Original - Software or previous Modifications; - - B. Any new file that contains any part of the Original - Software or previous Modifications; or - - C. Any new file that is contributed or otherwise made - available under the terms of this License. - - 1.10. "Original Software" means the Source Code and Executable - form of computer software code that is originally released - under this License. - - 1.11. "Patent Claims" means any patent claim(s), now owned or - hereafter acquired, including without limitation, method, - process, and apparatus claims, in any patent Licensable by - grantor. - - 1.12. "Source Code" means (a) the common form of computer software - code in which modifications are made and (b) associated - documentation included in or with such code. - - 1.13. "You" (or "Your") means an individual or a legal entity - exercising rights under, and complying with all of the terms - of, this License. For legal entities, "You" includes any - entity which controls, is controlled by, or is under common - control with You. For purposes of this definition, - "control" means (a) the power, direct or indirect, to cause - the direction or management of such entity, whether by - contract or otherwise, or (b) ownership of more than fifty - percent (50%) of the outstanding shares or beneficial - ownership of such entity. - -2. License Grants. - - 2.1. The Initial Developer Grant. - - Conditioned upon Your compliance with Section 3.1 below and - subject to third party intellectual property claims, the Initial - Developer hereby grants You a world-wide, royalty-free, - non-exclusive license: - - (a) under intellectual property rights (other than patent or - trademark) Licensable by Initial Developer, to use, - reproduce, modify, display, perform, sublicense and - distribute the Original Software (or portions thereof), - with or without Modifications, and/or as part of a Larger - Work; and - - (b) under Patent Claims infringed by the making, using or - selling of Original Software, to make, have made, use, - practice, sell, and offer for sale, and/or otherwise - dispose of the Original Software (or portions thereof). - - (c) The licenses granted in Sections 2.1(a) and (b) are - effective on the date Initial Developer first distributes - or otherwise makes the Original Software available to a - third party under the terms of this License. - - (d) Notwithstanding Section 2.1(b) above, no patent license is - granted: (1) for code that You delete from the Original - Software, or (2) for infringements caused by: (i) the - modification of the Original Software, or (ii) the - combination of the Original Software with other software - or devices. - - 2.2. Contributor Grant. - - Conditioned upon Your compliance with Section 3.1 below and - subject to third party intellectual property claims, each - Contributor hereby grants You a world-wide, royalty-free, - non-exclusive license: - - (a) under intellectual property rights (other than patent or - trademark) Licensable by Contributor to use, reproduce, - modify, display, perform, sublicense and distribute the - Modifications created by such Contributor (or portions - thereof), either on an unmodified basis, with other - Modifications, as Covered Software and/or as part of a - Larger Work; and - - (b) under Patent Claims infringed by the making, using, or - selling of Modifications made by that Contributor either - alone and/or in combination with its Contributor Version - (or portions of such combination), to make, use, sell, - offer for sale, have made, and/or otherwise dispose of: - (1) Modifications made by that Contributor (or portions - thereof); and (2) the combination of Modifications made by - that Contributor with its Contributor Version (or portions - of such combination). - - (c) The licenses granted in Sections 2.2(a) and 2.2(b) are - effective on the date Contributor first distributes or - otherwise makes the Modifications available to a third - party. - - (d) Notwithstanding Section 2.2(b) above, no patent license is - granted: (1) for any code that Contributor has deleted - from the Contributor Version; (2) for infringements caused - by: (i) third party modifications of Contributor Version, - or (ii) the combination of Modifications made by that - Contributor with other software (except as part of the - Contributor Version) or other devices; or (3) under Patent - Claims infringed by Covered Software in the absence of - Modifications made by that Contributor. - -3. Distribution Obligations. - - 3.1. Availability of Source Code. - - Any Covered Software that You distribute or otherwise make - available in Executable form must also be made available in Source - Code form and that Source Code form must be distributed only under - the terms of this License. You must include a copy of this - License with every copy of the Source Code form of the Covered - Software You distribute or otherwise make available. You must - inform recipients of any such Covered Software in Executable form - as to how they can obtain such Covered Software in Source Code - form in a reasonable manner on or through a medium customarily - used for software exchange. - - 3.2. Modifications. - - The Modifications that You create or to which You contribute are - governed by the terms of this License. You represent that You - believe Your Modifications are Your original creation(s) and/or - You have sufficient rights to grant the rights conveyed by this - License. - - 3.3. Required Notices. - - You must include a notice in each of Your Modifications that - identifies You as the Contributor of the Modification. You may - not remove or alter any copyright, patent or trademark notices - contained within the Covered Software, or any notices of licensing - or any descriptive text giving attribution to any Contributor or - the Initial Developer. - - 3.4. Application of Additional Terms. - - You may not offer or impose any terms on any Covered Software in - Source Code form that alters or restricts the applicable version - of this License or the recipients' rights hereunder. You may - choose to offer, and to charge a fee for, warranty, support, - indemnity or liability obligations to one or more recipients of - Covered Software. However, you may do so only on Your own behalf, - and not on behalf of the Initial Developer or any Contributor. - You must make it absolutely clear that any such warranty, support, - indemnity or liability obligation is offered by You alone, and You - hereby agree to indemnify the Initial Developer and every - Contributor for any liability incurred by the Initial Developer or - such Contributor as a result of warranty, support, indemnity or - liability terms You offer. - - 3.5. Distribution of Executable Versions. - - You may distribute the Executable form of the Covered Software - under the terms of this License or under the terms of a license of - Your choice, which may contain terms different from this License, - provided that You are in compliance with the terms of this License - and that the license for the Executable form does not attempt to - limit or alter the recipient's rights in the Source Code form from - the rights set forth in this License. If You distribute the - Covered Software in Executable form under a different license, You - must make it absolutely clear that any terms which differ from - this License are offered by You alone, not by the Initial - Developer or Contributor. You hereby agree to indemnify the - Initial Developer and every Contributor for any liability incurred - by the Initial Developer or such Contributor as a result of any - such terms You offer. - - 3.6. Larger Works. - - You may create a Larger Work by combining Covered Software with - other code not governed by the terms of this License and - distribute the Larger Work as a single product. In such a case, - You must make sure the requirements of this License are fulfilled - for the Covered Software. - -4. Versions of the License. - - 4.1. New Versions. - - Sun Microsystems, Inc. is the initial license steward and may - publish revised and/or new versions of this License from time to - time. Each version will be given a distinguishing version number. - Except as provided in Section 4.3, no one other than the license - steward has the right to modify this License. - - 4.2. Effect of New Versions. - - You may always continue to use, distribute or otherwise make the - Covered Software available under the terms of the version of the - License under which You originally received the Covered Software. - If the Initial Developer includes a notice in the Original - Software prohibiting it from being distributed or otherwise made - available under any subsequent version of the License, You must - distribute and make the Covered Software available under the terms - of the version of the License under which You originally received - the Covered Software. Otherwise, You may also choose to use, - distribute or otherwise make the Covered Software available under - the terms of any subsequent version of the License published by - the license steward. - - 4.3. Modified Versions. - - When You are an Initial Developer and You want to create a new - license for Your Original Software, You may create and use a - modified version of this License if You: (a) rename the license - and remove any references to the name of the license steward - (except to note that the license differs from this License); and - (b) otherwise make it clear that the license contains terms which - differ from this License. - -5. DISCLAIMER OF WARRANTY. - - COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" - BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, - INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED - SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR - PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND - PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY - COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE - INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY - NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF - WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF - ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS - DISCLAIMER. - -6. TERMINATION. - - 6.1. This License and the rights granted hereunder will terminate - automatically if You fail to comply with terms herein and fail to - cure such breach within 30 days of becoming aware of the breach. - Provisions which, by their nature, must remain in effect beyond - the termination of this License shall survive. - - 6.2. If You assert a patent infringement claim (excluding - declaratory judgment actions) against Initial Developer or a - Contributor (the Initial Developer or Contributor against whom You - assert such claim is referred to as "Participant") alleging that - the Participant Software (meaning the Contributor Version where - the Participant is a Contributor or the Original Software where - the Participant is the Initial Developer) directly or indirectly - infringes any patent, then any and all rights granted directly or - indirectly to You by such Participant, the Initial Developer (if - the Initial Developer is not the Participant) and all Contributors - under Sections 2.1 and/or 2.2 of this License shall, upon 60 days - notice from Participant terminate prospectively and automatically - at the expiration of such 60 day notice period, unless if within - such 60 day period You withdraw Your claim with respect to the - Participant Software against such Participant either unilaterally - or pursuant to a written agreement with Participant. - - 6.3. In the event of termination under Sections 6.1 or 6.2 above, - all end user licenses that have been validly granted by You or any - distributor hereunder prior to termination (excluding licenses - granted to You by any distributor) shall survive termination. - -7. LIMITATION OF LIABILITY. - - UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT - (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE - INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF - COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE - LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR - CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT - LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK - STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER - COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN - INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF - LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL - INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT - APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO - NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR - CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT - APPLY TO YOU. - -8. U.S. GOVERNMENT END USERS. - - The Covered Software is a "commercial item," as that term is - defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial - computer software" (as that term is defined at 48 - C.F.R. 252.227-7014(a)(1)) and "commercial computer software - documentation" as such terms are used in 48 C.F.R. 12.212 - (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 - C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all - U.S. Government End Users acquire Covered Software with only those - rights set forth herein. This U.S. Government Rights clause is in - lieu of, and supersedes, any other FAR, DFAR, or other clause or - provision that addresses Government rights in computer software - under this License. - -9. MISCELLANEOUS. - - This License represents the complete agreement concerning subject - matter hereof. If any provision of this License is held to be - unenforceable, such provision shall be reformed only to the extent - necessary to make it enforceable. This License shall be governed - by the law of the jurisdiction specified in a notice contained - within the Original Software (except to the extent applicable law, - if any, provides otherwise), excluding such jurisdiction's - conflict-of-law provisions. Any litigation relating to this - License shall be subject to the jurisdiction of the courts located - in the jurisdiction and venue specified in a notice contained - within the Original Software, with the losing party responsible - for costs, including, without limitation, court costs and - reasonable attorneys' fees and expenses. The application of the - United Nations Convention on Contracts for the International Sale - of Goods is expressly excluded. Any law or regulation which - provides that the language of a contract shall be construed - against the drafter shall not apply to this License. You agree - that You alone are responsible for compliance with the United - States export administration regulations (and the export control - laws and regulation of any other countries) when You use, - distribute or otherwise make available any Covered Software. - -10. RESPONSIBILITY FOR CLAIMS. - - As between Initial Developer and the Contributors, each party is - responsible for claims and damages arising, directly or - indirectly, out of its utilization of rights under this License - and You agree to work with Initial Developer and Contributors to - distribute such responsibility on an equitable basis. Nothing - herein is intended or shall be deemed to constitute any admission - of liability. - --------------------------------------------------------------------- - -NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND -DISTRIBUTION LICENSE (CDDL) - -For Covered Software in this distribution, this License shall -be governed by the laws of the State of California (excluding -conflict-of-law provisions). - -Any litigation relating to this License shall be subject to the -jurisdiction of the Federal Courts of the Northern District of -California and the state courts of the State of California, with -venue lying in Santa Clara County, California. diff --git a/licenses/github.com/imdario/mergo/LICENSE b/licenses/github.com/imdario/mergo/LICENSE deleted file mode 100644 index 686680298d..0000000000 --- a/licenses/github.com/imdario/mergo/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -Copyright (c) 2013 Dario Castañé. All rights reserved. -Copyright (c) 2012 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licenses/github.com/inconshreveable/mousetrap/LICENSE b/licenses/github.com/inconshreveable/mousetrap/LICENSE deleted file mode 100644 index 5f0d1fb6a7..0000000000 --- a/licenses/github.com/inconshreveable/mousetrap/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2014 Alan Shreve - -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. diff --git a/licenses/github.com/json-iterator/go/LICENSE b/licenses/github.com/json-iterator/go/LICENSE deleted file mode 100644 index 2cf4f5ab28..0000000000 --- a/licenses/github.com/json-iterator/go/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2016 json-iterator - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/licenses/github.com/juju/ratelimit/LICENSE b/licenses/github.com/juju/ratelimit/LICENSE deleted file mode 100644 index ade9307b39..0000000000 --- a/licenses/github.com/juju/ratelimit/LICENSE +++ /dev/null @@ -1,191 +0,0 @@ -All files in this repository are licensed as follows. If you contribute -to this repository, it is assumed that you license your contribution -under the same license unless you state otherwise. - -All files Copyright (C) 2015 Canonical Ltd. unless otherwise specified in the file. - -This software is licensed under the LGPLv3, included below. - -As a special exception to the GNU Lesser General Public License version 3 -("LGPL3"), the copyright holders of this Library give you permission to -convey to a third party a Combined Work that links statically or dynamically -to this Library without providing any Minimal Corresponding Source or -Minimal Application Code as set out in 4d or providing the installation -information set out in section 4e, provided that you comply with the other -provisions of LGPL3 and provided that you meet, for the Application the -terms and conditions of the license(s) which apply to the Application. - -Except as stated in this special exception, the provisions of LGPL3 will -continue to comply in full to this Library. If you modify this Library, you -may apply this exception to your version of this Library, but you are not -obliged to do so. If you do not wish to do so, delete this exception -statement from your version. This exception does not (and cannot) modify any -license terms which apply to the Application, with which you must still -comply. - - - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff --git a/licenses/github.com/lib/pq/LICENSE.md b/licenses/github.com/lib/pq/LICENSE.md deleted file mode 100644 index 5773904a30..0000000000 --- a/licenses/github.com/lib/pq/LICENSE.md +++ /dev/null @@ -1,8 +0,0 @@ -Copyright (c) 2011-2013, 'pq' Contributors -Portions Copyright (C) 2011 Blake Mizerany - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/licenses/github.com/mailru/easyjson/LICENSE b/licenses/github.com/mailru/easyjson/LICENSE deleted file mode 100644 index fbff658f70..0000000000 --- a/licenses/github.com/mailru/easyjson/LICENSE +++ /dev/null @@ -1,7 +0,0 @@ -Copyright (c) 2016 Mail.Ru Group - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/licenses/github.com/mattn/go-colorable/LICENSE b/licenses/github.com/mattn/go-colorable/LICENSE deleted file mode 100644 index 91b5cef30e..0000000000 --- a/licenses/github.com/mattn/go-colorable/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Yasuhiro Matsumoto - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/licenses/github.com/mattn/go-isatty/LICENSE b/licenses/github.com/mattn/go-isatty/LICENSE deleted file mode 100644 index 65dc692b6b..0000000000 --- a/licenses/github.com/mattn/go-isatty/LICENSE +++ /dev/null @@ -1,9 +0,0 @@ -Copyright (c) Yasuhiro MATSUMOTO - -MIT License (Expat) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/licenses/github.com/modern-go/concurrent/LICENSE b/licenses/github.com/modern-go/concurrent/LICENSE deleted file mode 100644 index 261eeb9e9f..0000000000 --- a/licenses/github.com/modern-go/concurrent/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/licenses/github.com/modern-go/reflect2/LICENSE b/licenses/github.com/modern-go/reflect2/LICENSE deleted file mode 100644 index 261eeb9e9f..0000000000 --- a/licenses/github.com/modern-go/reflect2/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/licenses/github.com/petar/GoLLRB/LICENSE b/licenses/github.com/petar/GoLLRB/LICENSE deleted file mode 100644 index b75312c787..0000000000 --- a/licenses/github.com/petar/GoLLRB/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2010, Petar Maymounkov -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -(*) Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -(*) Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -(*) Neither the name of Petar Maymounkov nor the names of its contributors may be -used to endorse or promote products derived from this software without specific -prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licenses/github.com/peterbourgon/diskv/LICENSE b/licenses/github.com/peterbourgon/diskv/LICENSE deleted file mode 100644 index 41ce7f16e1..0000000000 --- a/licenses/github.com/peterbourgon/diskv/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2011-2012 Peter Bourgon - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/licenses/github.com/russross/blackfriday/LICENSE.txt b/licenses/github.com/russross/blackfriday/LICENSE.txt deleted file mode 100644 index 2885af3602..0000000000 --- a/licenses/github.com/russross/blackfriday/LICENSE.txt +++ /dev/null @@ -1,29 +0,0 @@ -Blackfriday is distributed under the Simplified BSD License: - -> Copyright © 2011 Russ Ross -> All rights reserved. -> -> Redistribution and use in source and binary forms, with or without -> modification, are permitted provided that the following conditions -> are met: -> -> 1. Redistributions of source code must retain the above copyright -> notice, this list of conditions and the following disclaimer. -> -> 2. Redistributions in binary form must reproduce the above -> copyright notice, this list of conditions and the following -> disclaimer in the documentation and/or other materials provided with -> the distribution. -> -> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -> "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -> LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -> FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -> COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -> INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -> BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -> LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -> LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -> ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -> POSSIBILITY OF SUCH DAMAGE. diff --git a/licenses/github.com/sirupsen/logrus/LICENSE b/licenses/github.com/sirupsen/logrus/LICENSE deleted file mode 100644 index f090cb42f3..0000000000 --- a/licenses/github.com/sirupsen/logrus/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Simon Eskildsen - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/licenses/github.com/spf13/cobra/LICENSE.txt b/licenses/github.com/spf13/cobra/LICENSE.txt deleted file mode 100644 index 298f0e2665..0000000000 --- a/licenses/github.com/spf13/cobra/LICENSE.txt +++ /dev/null @@ -1,174 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. diff --git a/licenses/github.com/spf13/pflag/LICENSE b/licenses/github.com/spf13/pflag/LICENSE deleted file mode 100644 index 63ed1cfea1..0000000000 --- a/licenses/github.com/spf13/pflag/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -Copyright (c) 2012 Alex Ogier. All rights reserved. -Copyright (c) 2012 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licenses/golang.org/x/crypto/LICENSE b/licenses/golang.org/x/crypto/LICENSE deleted file mode 100644 index 6a66aea5ea..0000000000 --- a/licenses/golang.org/x/crypto/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2009 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licenses/golang.org/x/net/LICENSE b/licenses/golang.org/x/net/LICENSE deleted file mode 100644 index 6a66aea5ea..0000000000 --- a/licenses/golang.org/x/net/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2009 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licenses/golang.org/x/sys/LICENSE b/licenses/golang.org/x/sys/LICENSE deleted file mode 100644 index 6a66aea5ea..0000000000 --- a/licenses/golang.org/x/sys/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2009 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licenses/golang.org/x/text/LICENSE b/licenses/golang.org/x/text/LICENSE deleted file mode 100644 index 6a66aea5ea..0000000000 --- a/licenses/golang.org/x/text/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2009 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licenses/gopkg.in/inf.v0/LICENSE b/licenses/gopkg.in/inf.v0/LICENSE deleted file mode 100644 index 87a5cede33..0000000000 --- a/licenses/gopkg.in/inf.v0/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -Copyright (c) 2012 Péter Surányi. Portions Copyright (c) 2009 The Go -Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licenses/gopkg.in/robfig/cron.v2/LICENSE b/licenses/gopkg.in/robfig/cron.v2/LICENSE deleted file mode 100644 index 3a0f627ffe..0000000000 --- a/licenses/gopkg.in/robfig/cron.v2/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -Copyright (C) 2012 Rob Figueiredo -All Rights Reserved. - -MIT LICENSE - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/licenses/gopkg.in/yaml.v2/LICENSE b/licenses/gopkg.in/yaml.v2/LICENSE deleted file mode 100644 index 8dada3edaf..0000000000 --- a/licenses/gopkg.in/yaml.v2/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/licenses/k8s.io/api/LICENSE b/licenses/k8s.io/api/LICENSE deleted file mode 100644 index d645695673..0000000000 --- a/licenses/k8s.io/api/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/licenses/k8s.io/apimachinery/LICENSE b/licenses/k8s.io/apimachinery/LICENSE deleted file mode 100644 index d645695673..0000000000 --- a/licenses/k8s.io/apimachinery/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/licenses/k8s.io/client-go/LICENSE b/licenses/k8s.io/client-go/LICENSE deleted file mode 100644 index d645695673..0000000000 --- a/licenses/k8s.io/client-go/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/licenses/k8s.io/kube-openapi/LICENSE b/licenses/k8s.io/kube-openapi/LICENSE deleted file mode 100644 index d645695673..0000000000 --- a/licenses/k8s.io/kube-openapi/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. From 051cddb34b4abcfe6d46d05744aa97658afb75c0 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Thu, 29 Apr 2021 14:50:21 -0400 Subject: [PATCH 226/276] Update defaults for running aggregator script If $GOPATH is unset, this will default to using the standard module GOPATH. --- bin/license_aggregator.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/license_aggregator.sh b/bin/license_aggregator.sh index 044f8c016c..c965e51940 100755 --- a/bin/license_aggregator.sh +++ b/bin/license_aggregator.sh @@ -14,7 +14,7 @@ # limitations under the License. # Inputs / outputs -SCAN_DIR=${GOPATH}/pkg/mod +SCAN_DIR=${GOPATH:-~/go}/pkg/mod OUT_DIR=licenses # Fail on error From fc5d9f40c679d64ff500d452818a749e8aedeaf4 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 2 May 2021 13:24:20 -0400 Subject: [PATCH 227/276] Small tweak to script This ensures the file perms on LICENSE.txt do not change when script is run. --- bin/license_aggregator.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/license_aggregator.sh b/bin/license_aggregator.sh index c965e51940..c16070cfa7 100755 --- a/bin/license_aggregator.sh +++ b/bin/license_aggregator.sh @@ -36,3 +36,4 @@ do done sudo chmod -R 755 licenses +sudo chmod 0644 licenses/LICENSE.txt From e19a02473b566321ccf7b3058593de1a276bfa18 Mon Sep 17 00:00:00 2001 From: Val Date: Mon, 10 May 2021 22:19:18 -0400 Subject: [PATCH 228/276] Add explicit disable_fsgroup to OpenShift 3.11 manifest Add `disable_fsgroup: "true"` for the OpenShift 3.11 installer. --- installers/kubectl/postgres-operator-ocp311.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/installers/kubectl/postgres-operator-ocp311.yml b/installers/kubectl/postgres-operator-ocp311.yml index 780a1d1ada..1c2ec9e614 100644 --- a/installers/kubectl/postgres-operator-ocp311.yml +++ b/installers/kubectl/postgres-operator-ocp311.yml @@ -60,6 +60,7 @@ data: delete_operator_namespace: "false" delete_watched_namespaces: "false" disable_auto_failover: "false" + disable_fsgroup: "true" reconcile_rbac: "true" exporterport: "9187" metrics: "false" From c3d5af6fa0d0d6661af46a8b7ec91ae60ac77af8 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Thu, 13 May 2021 18:16:30 -0400 Subject: [PATCH 229/276] Fix panic when parsing backup options with extra space There existed a condition where checking for new backup options in the "pgo backup" command could cause a panic. This resolves the issue both by adding a guard on the array and trimming whitespace. --- internal/apiserver/backupoptions/backupoptionsutil.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/apiserver/backupoptions/backupoptionsutil.go b/internal/apiserver/backupoptions/backupoptionsutil.go index 0ce5e39be9..908aa1edb9 100644 --- a/internal/apiserver/backupoptions/backupoptionsutil.go +++ b/internal/apiserver/backupoptions/backupoptionsutil.go @@ -36,7 +36,8 @@ type backupOptions interface { // and restore utilities supported by pgo (e.g. pg_dump, pg_restore, pgBackRest, etc.) func ValidateBackupOpts(backupOpts string, request interface{}) error { // some quick checks to make sure backup opts string is valid and should be processed and validated - if strings.TrimSpace(backupOpts) == "" { + backupOpts = strings.TrimSpace(backupOpts) + if backupOpts == "" { return nil } else if !strings.HasPrefix(strings.TrimSpace(backupOpts), "-") && !strings.HasPrefix(strings.TrimSpace(backupOpts), "--") { @@ -112,7 +113,7 @@ func parseBackupOpts(backupOpts string) []string { var newField string for i, c := range backupOpts { // if another option is found, add current option to newFields array - if !(c == ' ' && backupOpts[i+1] == '-') { + if !(c == ' ' && i+1 < len(backupOpts) && backupOpts[i+1] == '-') { newField = newField + string(c) } From 4f3ce521616592fa7ff4639ad684348a02d78a8f Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Fri, 21 May 2021 10:57:52 -0400 Subject: [PATCH 230/276] Bump v4.6.3-rc.1 --- Makefile | 4 +-- README.md | 2 +- bin/push-ccp-to-gcr.sh | 2 +- conf/postgres-operator/pgo.yaml | 4 +-- docs/config.toml | 6 ++-- docs/content/Configuration/compatibility.md | 6 ++++ docs/content/releases/4.6.3.md | 29 +++++++++++++++++++ examples/create-by-resource/fromcrd.json | 6 ++-- examples/envs.sh | 2 +- examples/helm/README.md | 4 +-- examples/helm/postgres/Chart.yaml | 2 +- .../helm/postgres/templates/pgcluster.yaml | 2 +- examples/helm/postgres/values.yaml | 2 +- examples/kustomize/createcluster/README.md | 16 +++++----- .../createcluster/base/pgcluster.yaml | 6 ++-- .../overlay/staging/hippo-rpl1-pgreplica.yaml | 2 +- installers/ansible/README.md | 2 +- installers/ansible/values.yaml | 6 ++-- installers/gcp-marketplace/Makefile | 2 +- installers/gcp-marketplace/README.md | 2 +- installers/gcp-marketplace/values.yaml | 6 ++-- installers/helm/Chart.yaml | 2 +- installers/helm/values.yaml | 6 ++-- installers/kubectl/client-setup.sh | 2 +- .../kubectl/postgres-operator-ocp311.yml | 8 ++--- installers/kubectl/postgres-operator.yml | 8 ++--- installers/metrics/ansible/README.md | 2 +- installers/metrics/helm/Chart.yaml | 2 +- installers/metrics/helm/helm_template.yaml | 2 +- installers/metrics/helm/values.yaml | 2 +- .../postgres-operator-metrics-ocp311.yml | 2 +- .../kubectl/postgres-operator-metrics.yml | 2 +- installers/olm/Makefile | 4 +-- installers/olm/description.openshift.md | 2 +- installers/olm/description.upstream.md | 2 +- internal/operator/cluster/upgrade.go | 2 +- pkg/apis/crunchydata.com/v1/doc.go | 8 ++--- pkg/apiservermsgs/common.go | 2 +- redhat/atomic/help.1 | 2 +- redhat/atomic/help.md | 2 +- 40 files changed, 105 insertions(+), 70 deletions(-) create mode 100644 docs/content/releases/4.6.3.md diff --git a/Makefile b/Makefile index 071054389d..bf7d3dcabe 100644 --- a/Makefile +++ b/Makefile @@ -5,9 +5,9 @@ PGOROOT ?= $(CURDIR) PGO_BASEOS ?= centos8 PGO_IMAGE_PREFIX ?= crunchydata PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) -PGO_VERSION ?= 4.6.2 +PGO_VERSION ?= 4.6.3-rc.1 PGO_PG_VERSION ?= 13 -PGO_PG_FULLVERSION ?= 13.2 +PGO_PG_FULLVERSION ?= 13.3 PGO_BACKREST_VERSION ?= 2.31 PACKAGER ?= yum diff --git a/README.md b/README.md index 4ecf709b77..4db69e50b5 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ to start as quickly as: ```shell kubectl create namespace pgo -kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.2/installers/kubectl/postgres-operator.yml +kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.3-rc.1/installers/kubectl/postgres-operator.yml ``` Otherwise, we highly recommend following the instructions from our [Quickstart](https://access.crunchydata.com/documentation/postgres-operator/latest/quickstart/). diff --git a/bin/push-ccp-to-gcr.sh b/bin/push-ccp-to-gcr.sh index 60b0332e5a..46f0c74062 100755 --- a/bin/push-ccp-to-gcr.sh +++ b/bin/push-ccp-to-gcr.sh @@ -16,7 +16,7 @@ GCR_IMAGE_PREFIX=gcr.io/crunchy-dev-test CCP_IMAGE_PREFIX=crunchydata -CCP_IMAGE_TAG=centos8-13.2-4.6.2 +CCP_IMAGE_TAG=centos8-13.3-4.6.3-rc.1 IMAGES=( crunchy-prometheus diff --git a/conf/postgres-operator/pgo.yaml b/conf/postgres-operator/pgo.yaml index ade19a59f2..9d54c59530 100644 --- a/conf/postgres-operator/pgo.yaml +++ b/conf/postgres-operator/pgo.yaml @@ -2,7 +2,7 @@ Cluster: CCPImagePrefix: registry.developers.crunchydata.com/crunchydata Metrics: false Badger: false - CCPImageTag: centos8-13.2-4.6.2 + CCPImageTag: centos8-13.3-4.6.3-rc.1 Port: 5432 PGBadgerPort: 10000 ExporterPort: 9187 @@ -80,4 +80,4 @@ Storage: Pgo: Audit: false PGOImagePrefix: registry.developers.crunchydata.com/crunchydata - PGOImageTag: centos8-4.6.2 + PGOImageTag: centos8-4.6.3-rc.1 diff --git a/docs/config.toml b/docs/config.toml index af3d2d8607..70e630d6d2 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -25,9 +25,9 @@ disableNavChevron = false # set true to hide next/prev chevron, default is false highlightClientSide = false # set true to use highlight.pack.js instead of the default hugo chroma highlighter menushortcutsnewtab = true # set true to open shortcuts links to a new tab/window enableGitInfo = true -operatorVersion = "4.6.2" -postgresVersion = "13.2" -postgresVersion13 = "13.2" +operatorVersion = "4.6.3-rc.1" +postgresVersion = "13.3" +postgresVersion13 = "13.3" postgresVersion12 = "12.6" postgresVersion11 = "11.11" postgresVersion10 = "10.16" diff --git a/docs/content/Configuration/compatibility.md b/docs/content/Configuration/compatibility.md index 7be9a787e6..de40a08b70 100644 --- a/docs/content/Configuration/compatibility.md +++ b/docs/content/Configuration/compatibility.md @@ -12,6 +12,12 @@ version dependencies between the two projects. Below are the operator releases a | Operator Release | Container Release | Postgres | PgBackrest Version |:----------|:-------------|:------------|:-------------- +| 4.6.3 | 4.6.3 | 13.3 | 2.31 | +|||12.7|2.31| +|||11.12|2.31| +|||10.17|2.31| +|||9.6.22|2.31| +|||| | 4.6.2 | 4.6.2 | 13.2 | 2.31 | |||12.6|2.31| |||11.11|2.31| diff --git a/docs/content/releases/4.6.3.md b/docs/content/releases/4.6.3.md new file mode 100644 index 0000000000..652ce4fc7a --- /dev/null +++ b/docs/content/releases/4.6.3.md @@ -0,0 +1,29 @@ +--- +title: "4.6.3" +date: +draft: false +weight: 57 +--- + +Crunchy Data announces the release of the PostgreSQL Operator 4.6.3 on May DD, 2021. + +The PostgreSQL Operator is released in conjunction with the [Crunchy Container Suite](https://github.com/CrunchyData/crunchy-containers/). + +PostgreSQL Operator 4.6.3 release includes the following software versions upgrades: + +- [PostgreSQL](https://www.postgresql.org/) is now at 13.3, 12.7, 11.12, 10.17, and 9.6.22. +- [pgMonitor](https://github.com/CrunchyData/pgmonitor) is now at 4.4-1. + +## Changes + +- Allow for the `PGOADMIN_USERNAME`, `PGOADMIN_PASSWORD`, `PGOADMIN_ROLENAME` credential bootstrap variables to be overriden as part of the OLM and development install process. Contributed by Mathieu Parent (@sathieu). +- Update Helm installer to follow appropriate conventions. Contributed by Jakub Ráček (@kubaracek) + +## Fixes + +- Fix crash due to superfluous trailing whitespace when parsing `--backup-opts` in `pgo backup`. Reported by Samir Faci (@safaci2000). +- Ensure `sshd_config` is correctly set on an upgrade. This could have manifested with some pgBackRest functionality not working. This can be manually fixed by setting `UsePAM no` in the `sshd_config` file in a cluster. Reported by (@douggutaby). +- Fix issue where metrics about pgBackRest backups could not be scraped if the backups were stored in a S3-like environment that requires the use of disabling TLS verification. Reported by (@lphan-clv) and (@dakine1111). +- Fix how the pgAdmin 4 Service is identified in `pgo test`. Prior to this, it was identified as a "primary"; now it is "pgadmin". +- Fix nonbreaking error message that occurs when `pgo-scheduler` container shuts down in the UBI 8 base container. +- The `pgo-deployer` and Ansible installer will no longer create an initial TLS secret for the PGO apiserver. PGO apiserver has been able to self-create this for a long time, and PGO defers to that. This fixes an issue that occurred on newer builds where certificates generated by OpenSSL contained incomplete usage blocks, which could cause for these certificates to be properly outright rejected. diff --git a/examples/create-by-resource/fromcrd.json b/examples/create-by-resource/fromcrd.json index 39e0b788fe..e0a28bb910 100644 --- a/examples/create-by-resource/fromcrd.json +++ b/examples/create-by-resource/fromcrd.json @@ -10,7 +10,7 @@ "deployment-name": "fromcrd", "name": "fromcrd", "pg-cluster": "fromcrd", - "pgo-version": "4.6.2", + "pgo-version": "4.6.3-rc.1", "pgouser": "pgoadmin" }, "name": "fromcrd", @@ -45,7 +45,7 @@ "supplementalgroups": "" }, "ccpimage": "crunchy-postgres-ha", - "ccpimagetag": "centos8-13.2-4.6.2", + "ccpimagetag": "centos8-13.3-4.6.3-rc.1", "clustername": "fromcrd", "database": "userdb", "exporterport": "9187", @@ -60,7 +60,7 @@ "port": "5432", "user": "testuser", "userlabels": { - "pgo-version": "4.6.2" + "pgo-version": "4.6.3-rc.1" } } } diff --git a/examples/envs.sh b/examples/envs.sh index 646fd06e90..d9e1b2a923 100644 --- a/examples/envs.sh +++ b/examples/envs.sh @@ -20,7 +20,7 @@ export PGO_CONF_DIR=$PGOROOT/installers/ansible/roles/pgo-operator/files # the version of the Operator you run is set by these vars export PGO_IMAGE_PREFIX=registry.developers.crunchydata.com/crunchydata export PGO_BASEOS=centos8 -export PGO_VERSION=4.6.2 +export PGO_VERSION=4.6.3-rc.1 export PGO_IMAGE_TAG=$PGO_BASEOS-$PGO_VERSION # for setting the pgo apiserver port, disabling TLS or not verifying TLS diff --git a/examples/helm/README.md b/examples/helm/README.md index 4bf8a57bb3..235076842a 100644 --- a/examples/helm/README.md +++ b/examples/helm/README.md @@ -64,7 +64,7 @@ The following values can also be set: - `ha`: Whether or not to deploy a high availability PostgreSQL cluster. Can be either `true` or `false`, defaults to `false`. - `imagePrefix`: The prefix of the container images to use for this PostgreSQL cluster. Default to `registry.developers.crunchydata.com/crunchydata`. - `image`: The name of the container image to use for the PostgreSQL cluster. Defaults to `crunchy-postgres-ha`. -- `imageTag`: The container image tag to use. Defaults to `centos8-13.2-4.6.2`. +- `imageTag`: The container image tag to use. Defaults to `centos8-13.3-4.6.3-rc.1`. - `memory`: The memory limit for the PostgreSQL cluster. Follows standard Kubernetes formatting. - `monitoring`: Whether or not to enable monitoring / metrics collection for this PostgreSQL instance. Can either be `true` or `false`, defaults to `false`. @@ -103,7 +103,7 @@ PGPASSWORD="W4tch0ut4hippo$" psql -h localhost -U hippo hippo ## Notes -Prior to PostgreSQL Operator 4.6.2, you will have to manually clean up some of the artifacts when running `helm uninstall`. +Prior to PostgreSQL Operator 4.6.3-rc.1, you will have to manually clean up some of the artifacts when running `helm uninstall`. ## Additional Resources diff --git a/examples/helm/postgres/Chart.yaml b/examples/helm/postgres/Chart.yaml index e393b955ff..d9b60ce061 100644 --- a/examples/helm/postgres/Chart.yaml +++ b/examples/helm/postgres/Chart.yaml @@ -20,4 +20,4 @@ version: 0.2.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. -appVersion: 4.6.2 +appVersion: 4.6.3-rc.1 diff --git a/examples/helm/postgres/templates/pgcluster.yaml b/examples/helm/postgres/templates/pgcluster.yaml index 6df3537199..a0852741ba 100644 --- a/examples/helm/postgres/templates/pgcluster.yaml +++ b/examples/helm/postgres/templates/pgcluster.yaml @@ -28,7 +28,7 @@ spec: storagetype: dynamic ccpimage: {{ .Values.image | default "crunchy-postgres-ha" | quote }} ccpimageprefix: {{ .Values.imagePrefix | default "registry.developers.crunchydata.com/crunchydata" | quote }} - ccpimagetag: {{ .Values.imageTag | default "centos8-13.2-4.6.2" | quote }} + ccpimagetag: {{ .Values.imageTag | default "centos8-13.3-4.6.3-rc.1" | quote }} clustername: {{ .Values.name | quote }} database: {{ .Values.name | quote }} {{- if .Values.monitoring }} diff --git a/examples/helm/postgres/values.yaml b/examples/helm/postgres/values.yaml index a07e7dc2d3..51c855ed22 100644 --- a/examples/helm/postgres/values.yaml +++ b/examples/helm/postgres/values.yaml @@ -10,5 +10,5 @@ password: W4tch0ut4hippo$ # ha: true # imagePrefix: registry.developers.crunchydata.com/crunchydata # image: crunchy-postgres-ha -# imageTag: centos8-13.2-4.6.2 +# imageTag: centos8-13.3-4.6.3-rc.1 # memory: 1Gi diff --git a/examples/kustomize/createcluster/README.md b/examples/kustomize/createcluster/README.md index 75d095ebbc..e9c4891193 100644 --- a/examples/kustomize/createcluster/README.md +++ b/examples/kustomize/createcluster/README.md @@ -44,13 +44,13 @@ pgo show cluster hippo -n pgo ``` You will see something like this if successful: ``` -cluster : hippo (crunchy-postgres-ha:centos8-13.2-4.6.2) +cluster : hippo (crunchy-postgres-ha:centos8-13.3-4.6.3-rc.1) pod : hippo-8fb6bd96-j87wq (Running) on gke-xxxx-default-pool-38e946bd-257w (1/1) (primary) pvc: hippo (1Gi) deployment : hippo deployment : hippo-backrest-shared-repo service : hippo - ClusterIP (10.0.56.86) - Ports (2022/TCP, 5432/TCP) - labels : pgo-version=4.6.2 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.3-rc.1 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata ``` Feel free to run other pgo cli commands on the hippo cluster @@ -79,7 +79,7 @@ pgo show cluster dev-hippo -n pgo ``` You will see something like this if successful: ``` -cluster : dev-hippo (crunchy-postgres-ha:centos8-13.2-4.6.2) +cluster : dev-hippo (crunchy-postgres-ha:centos8-13.3-4.6.3-rc.1) pod : dev-hippo-588d4cb746-bwrxb (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: dev-hippo (1Gi) deployment : dev-hippo @@ -87,7 +87,7 @@ cluster : dev-hippo (crunchy-postgres-ha:centos8-13.2-4.6.2) deployment : dev-hippo-pgbouncer service : dev-hippo - ClusterIP (10.0.62.87) - Ports (2022/TCP, 5432/TCP) service : dev-hippo-pgbouncer - ClusterIP (10.0.48.120) - Ports (5432/TCP) - labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.6.2 pgouser=admin + labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.6.3-rc.1 pgouser=admin ``` #### staging The staging overlay will deploy a crunchy postgreSQL cluster with 2 replica's with annotations added @@ -113,7 +113,7 @@ pgo show cluster staging-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : staging-hippo (crunchy-postgres-ha:centos8-13.2-4.6.2) +cluster : staging-hippo (crunchy-postgres-ha:centos8-13.3-4.6.3-rc.1) pod : staging-hippo-85cf6dcb65-9h748 (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: staging-hippo (1Gi) pod : staging-hippo-lnxw-cf47d8c8b-6r4wn (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (replica) @@ -128,7 +128,7 @@ cluster : staging-hippo (crunchy-postgres-ha:centos8-13.2-4.6.2) service : staging-hippo-replica - ClusterIP (10.0.56.57) - Ports (2022/TCP, 5432/TCP) pgreplica : staging-hippo-lnxw pgreplica : staging-hippo-rpl1 - labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.6.2 pgouser=admin vendor=crunchydata + labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.6.3-rc.1 pgouser=admin vendor=crunchydata ``` #### production @@ -154,7 +154,7 @@ pgo show cluster prod-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : prod-hippo (crunchy-postgres-ha:centos8-13.2-4.6.2) +cluster : prod-hippo (crunchy-postgres-ha:centos8-13.3-4.6.3-rc.1) pod : prod-hippo-5d6dd46497-rr67c (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (primary) pvc: prod-hippo (1Gi) pod : prod-hippo-flty-84d97c8769-2pzbh (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (replica) @@ -165,7 +165,7 @@ cluster : prod-hippo (crunchy-postgres-ha:centos8-13.2-4.6.2) service : prod-hippo - ClusterIP (10.0.56.18) - Ports (2022/TCP, 5432/TCP) service : prod-hippo-replica - ClusterIP (10.0.56.101) - Ports (2022/TCP, 5432/TCP) pgreplica : prod-hippo-flty - labels : pgo-version=4.6.2 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.3-rc.1 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata ``` ### Delete the clusters To delete the clusters run the following pgo cli commands diff --git a/examples/kustomize/createcluster/base/pgcluster.yaml b/examples/kustomize/createcluster/base/pgcluster.yaml index cec2463c22..0dd69a0148 100644 --- a/examples/kustomize/createcluster/base/pgcluster.yaml +++ b/examples/kustomize/createcluster/base/pgcluster.yaml @@ -10,7 +10,7 @@ metadata: deployment-name: hippo name: hippo pg-cluster: hippo - pgo-version: 4.6.2 + pgo-version: 4.6.3-rc.1 pgouser: admin name: hippo namespace: pgo @@ -46,7 +46,7 @@ spec: postgres: {} ccpimage: crunchy-postgres-ha ccpimageprefix: registry.developers.crunchydata.com/crunchydata - ccpimagetag: centos8-13.2-4.6.2 + ccpimagetag: centos8-13.3-4.6.3-rc.1 clustername: hippo customconfig: "" database: hippo @@ -69,4 +69,4 @@ spec: port: "5432" user: hippo userlabels: - pgo-version: 4.6.2 + pgo-version: 4.6.3-rc.1 diff --git a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml index 32f1f62fd8..2867bd4cc1 100644 --- a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml +++ b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml @@ -20,4 +20,4 @@ spec: storagetype: dynamic supplementalgroups: "" userlabels: - pgo-version: 4.6.2 + pgo-version: 4.6.3-rc.1 diff --git a/installers/ansible/README.md b/installers/ansible/README.md index d57a482c5c..46ef61c2cc 100644 --- a/installers/ansible/README.md +++ b/installers/ansible/README.md @@ -4,7 +4,7 @@ PGO: The Postgres Operator from Crunchy Data

-Latest Release: 4.6.2 +Latest Release: 4.6.3-rc.1 ## General diff --git a/installers/ansible/values.yaml b/installers/ansible/values.yaml index 268d27d023..0d618a1571 100644 --- a/installers/ansible/values.yaml +++ b/installers/ansible/values.yaml @@ -17,7 +17,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.2-4.6.2" +ccp_image_tag: "centos8-13.3-4.6.3-rc.1" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -49,14 +49,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.2" +pgo_client_version: "4.6.3-rc.1" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos8-4.6.2" +pgo_image_tag: "centos8-4.6.3-rc.1" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/gcp-marketplace/Makefile b/installers/gcp-marketplace/Makefile index a931545979..5520eddac3 100644 --- a/installers/gcp-marketplace/Makefile +++ b/installers/gcp-marketplace/Makefile @@ -6,7 +6,7 @@ MARKETPLACE_TOOLS ?= gcr.io/cloud-marketplace-tools/k8s/dev:$(MARKETPLACE_VERSIO MARKETPLACE_VERSION ?= 0.9.4 KUBECONFIG ?= $(HOME)/.kube/config PARAMETERS ?= {} -PGO_VERSION ?= 4.6.2 +PGO_VERSION ?= 4.6.3-rc.1 IMAGE_BUILD_ARGS = --build-arg MARKETPLACE_VERSION='$(MARKETPLACE_VERSION)' \ --build-arg PGO_VERSION='$(PGO_VERSION)' diff --git a/installers/gcp-marketplace/README.md b/installers/gcp-marketplace/README.md index 1a75980180..60f31d4e9a 100644 --- a/installers/gcp-marketplace/README.md +++ b/installers/gcp-marketplace/README.md @@ -59,7 +59,7 @@ Google Cloud Marketplace. ```shell IMAGE_REPOSITORY=gcr.io/crunchydata-public/postgres-operator - export PGO_VERSION=4.6.2 + export PGO_VERSION=4.6.3-rc.1 export INSTALLER_IMAGE=${IMAGE_REPOSITORY}/deployer:${PGO_VERSION} export OPERATOR_IMAGE=${IMAGE_REPOSITORY}:${PGO_VERSION} export OPERATOR_IMAGE_API=${IMAGE_REPOSITORY}/pgo-apiserver:${PGO_VERSION} diff --git a/installers/gcp-marketplace/values.yaml b/installers/gcp-marketplace/values.yaml index d995c80fe2..5f20281ecc 100644 --- a/installers/gcp-marketplace/values.yaml +++ b/installers/gcp-marketplace/values.yaml @@ -10,7 +10,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.2-4.6.2" +ccp_image_tag: "centos8-13.3-4.6.3-rc.1" create_rbac: "true" db_name: "" db_password_age_days: "0" @@ -32,9 +32,9 @@ pgo_admin_role_name: "pgoadmin" pgo_admin_username: "admin" pgo_client_container_install: "false" pgo_client_install: 'false' -pgo_client_version: "4.6.2" +pgo_client_version: "4.6.3-rc.1" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.2" +pgo_image_tag: "centos8-4.6.3-rc.1" pgo_installation_name: '${OPERATOR_NAME}' pgo_operator_namespace: '${OPERATOR_NAMESPACE}' scheduler_timeout: "3600" diff --git a/installers/helm/Chart.yaml b/installers/helm/Chart.yaml index e1458b2beb..7ac33ac604 100644 --- a/installers/helm/Chart.yaml +++ b/installers/helm/Chart.yaml @@ -3,7 +3,7 @@ name: postgres-operator description: 'PGO: The Postgres Operator from Crunchy Data Helm Chart for Kubernetes' type: application version: 0.2.0 -appVersion: 4.6.2 +appVersion: 4.6.3-rc.1 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/docs/static/logos/pgo.svg keywords: diff --git a/installers/helm/values.yaml b/installers/helm/values.yaml index bb466303f1..2ecc55cd6a 100644 --- a/installers/helm/values.yaml +++ b/installers/helm/values.yaml @@ -37,7 +37,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.2-4.6.2" +ccp_image_tag: "centos8-13.3-4.6.3-rc.1" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -69,14 +69,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.2" +pgo_client_version: "4.6.3-rc.1" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos8-4.6.2" +pgo_image_tag: "centos8-4.6.3-rc.1" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/kubectl/client-setup.sh b/installers/kubectl/client-setup.sh index c1f077dda2..7f14dd18df 100755 --- a/installers/kubectl/client-setup.sh +++ b/installers/kubectl/client-setup.sh @@ -14,7 +14,7 @@ # This script should be run after the operator has been deployed PGO_OPERATOR_NAMESPACE="${PGO_OPERATOR_NAMESPACE:-pgo}" PGO_USER_ADMIN="${PGO_USER_ADMIN:-pgouser-admin}" -PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.2}" +PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.3-rc.1}" PGO_CLIENT_URL="https://github.com/CrunchyData/postgres-operator/releases/download/${PGO_CLIENT_VERSION}" PGO_CMD="${PGO_CMD-kubectl}" diff --git a/installers/kubectl/postgres-operator-ocp311.yml b/installers/kubectl/postgres-operator-ocp311.yml index 1c2ec9e614..e1250e5fd5 100644 --- a/installers/kubectl/postgres-operator-ocp311.yml +++ b/installers/kubectl/postgres-operator-ocp311.yml @@ -44,7 +44,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos8-13.2-4.6.2" + ccp_image_tag: "centos8-13.3-4.6.3-rc.1" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -77,14 +77,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.2" + pgo_client_version: "4.6.3-rc.1" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos8-4.6.2" + pgo_image_tag: "centos8-4.6.3-rc.1" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -161,7 +161,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.2 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.3-rc.1 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/kubectl/postgres-operator.yml b/installers/kubectl/postgres-operator.yml index e643ff20c6..ee621ef840 100644 --- a/installers/kubectl/postgres-operator.yml +++ b/installers/kubectl/postgres-operator.yml @@ -139,7 +139,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos8-13.2-4.6.2" + ccp_image_tag: "centos8-13.3-4.6.3-rc.1" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -172,14 +172,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.2" + pgo_client_version: "4.6.3-rc.1" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos8-4.6.2" + pgo_image_tag: "centos8-4.6.3-rc.1" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -269,7 +269,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.2 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.3-rc.1 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/ansible/README.md b/installers/metrics/ansible/README.md index f37baa0d2b..d3c5e0a349 100644 --- a/installers/metrics/ansible/README.md +++ b/installers/metrics/ansible/README.md @@ -4,7 +4,7 @@ Crunchy Data

-Latest Release: 4.6.2 +Latest Release: 4.6.3-rc.1 ## General diff --git a/installers/metrics/helm/Chart.yaml b/installers/metrics/helm/Chart.yaml index 3b6c6d85ab..af687070cf 100644 --- a/installers/metrics/helm/Chart.yaml +++ b/installers/metrics/helm/Chart.yaml @@ -3,6 +3,6 @@ name: postgres-operator-monitoring description: Install for Crunchy PostgreSQL Operator Monitoring type: application version: 0.2.0 -appVersion: 4.6.2 +appVersion: 4.6.3-rc.1 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/docs/static/logos/pgo.svg diff --git a/installers/metrics/helm/helm_template.yaml b/installers/metrics/helm/helm_template.yaml index 8926e386b1..0baff5e0f0 100644 --- a/installers/metrics/helm/helm_template.yaml +++ b/installers/metrics/helm/helm_template.yaml @@ -20,5 +20,5 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.2" +pgo_image_tag: "centos8-4.6.3-rc.1" diff --git a/installers/metrics/helm/values.yaml b/installers/metrics/helm/values.yaml index 2ff05b4a10..f1833db755 100644 --- a/installers/metrics/helm/values.yaml +++ b/installers/metrics/helm/values.yaml @@ -20,7 +20,7 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.2" +pgo_image_tag: "centos8-4.6.3-rc.1" # ===================== # Configuration Options diff --git a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml index 276e80914c..953a9777cb 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml @@ -96,7 +96,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.2 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.3-rc.1 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/kubectl/postgres-operator-metrics.yml b/installers/metrics/kubectl/postgres-operator-metrics.yml index 8d88788c43..953f778ace 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics.yml @@ -165,7 +165,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.2 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.3-rc.1 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/olm/Makefile b/installers/olm/Makefile index b4455d4a9e..955caec5d5 100644 --- a/installers/olm/Makefile +++ b/installers/olm/Makefile @@ -2,7 +2,7 @@ .SUFFIXES: CCP_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -CCP_PG_FULLVERSION ?= 13.2 +CCP_PG_FULLVERSION ?= 13.3 CCP_POSTGIS_VERSION ?= 3.0 CONTAINER ?= docker KUBECONFIG ?= $(HOME)/.kube/config @@ -11,7 +11,7 @@ OLM_TOOLS ?= registry.localhost:5000/postgres-operator-olm-tools:$(OLM_SDK_VERSI OLM_VERSION ?= 0.15.1 PGO_BASEOS ?= centos8 PGO_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -PGO_VERSION ?= 4.6.2 +PGO_VERSION ?= 4.6.3-rc.1 PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) CCP_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(PGO_VERSION) CCP_POSTGIS_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(CCP_POSTGIS_VERSION)-$(PGO_VERSION) diff --git a/installers/olm/description.openshift.md b/installers/olm/description.openshift.md index df14addbab..81c2238916 100644 --- a/installers/olm/description.openshift.md +++ b/installers/olm/description.openshift.md @@ -187,7 +187,7 @@ spec: annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: ${cluster_image_prefix} - ccpimagetag: centos8-13.2-${PGO_VERSION} + ccpimagetag: centos8-13.3-${PGO_VERSION} clustername: ${pgo_cluster_name} database: ${pgo_cluster_name} exporterport: "9187" diff --git a/installers/olm/description.upstream.md b/installers/olm/description.upstream.md index a9d28643e9..4c6d2484b5 100644 --- a/installers/olm/description.upstream.md +++ b/installers/olm/description.upstream.md @@ -167,7 +167,7 @@ spec: annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: ${cluster_image_prefix} - ccpimagetag: centos8-13.2-${PGO_VERSION} + ccpimagetag: centos8-13.3-${PGO_VERSION} clustername: ${pgo_cluster_name} database: ${pgo_cluster_name} exporterport: "9187" diff --git a/internal/operator/cluster/upgrade.go b/internal/operator/cluster/upgrade.go index c5a600815b..1c4ddd5638 100644 --- a/internal/operator/cluster/upgrade.go +++ b/internal/operator/cluster/upgrade.go @@ -953,7 +953,7 @@ func updatePGBackRestSSHDConfig(clientset kubernetes.Interface, repoSecret *v1.S ctx := context.TODO() updatedRepoSecret := repoSecret.DeepCopy() - // For versions prior to v4.6.2, the UsePAM setting might be set to 'yes' as previously + // For versions prior to v4.6.3-rc.1, the UsePAM setting might be set to 'yes' as previously // required to workaround a known Docker issue. Since this issue has since been resolved, // we now want to ensure this setting is set to 'no'. if !usePAMRegex.MatchString(string(updatedRepoSecret.Data["sshd_config"])) { diff --git a/pkg/apis/crunchydata.com/v1/doc.go b/pkg/apis/crunchydata.com/v1/doc.go index 3661730abe..8547301d36 100644 --- a/pkg/apis/crunchydata.com/v1/doc.go +++ b/pkg/apis/crunchydata.com/v1/doc.go @@ -53,7 +53,7 @@ cluster. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.2", + '{"ClientVersion":"4.6.3-rc.1", "Namespace":"pgouser1", "Name":"mycluster", $PGO_APISERVER_URL/clusters @@ -72,7 +72,7 @@ show all of the clusters that are in the given namespace. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.2", + '{"ClientVersion":"4.6.3-rc.1", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/showclusters @@ -82,7 +82,7 @@ $PGO_APISERVER_URL/showclusters curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.2", + '{"ClientVersion":"4.6.3-rc.1", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/clustersdelete @@ -90,7 +90,7 @@ $PGO_APISERVER_URL/clustersdelete Schemes: http, https BasePath: / - Version: 4.6.2 + Version: 4.6.3-rc.1 License: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0 Contact: Crunchy Data https://www.crunchydata.com/ diff --git a/pkg/apiservermsgs/common.go b/pkg/apiservermsgs/common.go index d57e12b9e2..879049694f 100644 --- a/pkg/apiservermsgs/common.go +++ b/pkg/apiservermsgs/common.go @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -const PGO_VERSION = "4.6.2" +const PGO_VERSION = "4.6.3-rc.1" // Ok status const Ok = "ok" diff --git a/redhat/atomic/help.1 b/redhat/atomic/help.1 index 20c51a73c5..e412118618 100644 --- a/redhat/atomic/help.1 +++ b/redhat/atomic/help.1 @@ -56,4 +56,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa \fB\fCRelease=\fR .PP -The specific release number of the container. For example, Release="4.6.2" +The specific release number of the container. For example, Release="4.6.3-rc.1" diff --git a/redhat/atomic/help.md b/redhat/atomic/help.md index 9e8350338a..5eebd59621 100644 --- a/redhat/atomic/help.md +++ b/redhat/atomic/help.md @@ -45,4 +45,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa `Release=` -The specific release number of the container. For example, Release="4.6.2" +The specific release number of the container. For example, Release="4.6.3-rc.1" From 95c983748276ab1bf62c05f367b26cf9c2735006 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Fri, 21 May 2021 13:16:17 -0400 Subject: [PATCH 231/276] Bump pgMonitor version --- bin/get-pgmonitor.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/get-pgmonitor.sh b/bin/get-pgmonitor.sh index e8cf4a0e02..f48d842399 100755 --- a/bin/get-pgmonitor.sh +++ b/bin/get-pgmonitor.sh @@ -14,7 +14,7 @@ # limitations under the License. echo "Getting pgMonitor..." -PGMONITOR_COMMIT='v4.4' +PGMONITOR_COMMIT='4.4-1' # pgMonitor Setup if [[ -d ${PGOROOT?}/tools/pgmonitor ]] From e9d7c5a6323235ffad82825831f10bca8c923295 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 24 May 2021 16:06:12 -0400 Subject: [PATCH 232/276] Bump v4.6.3 --- Makefile | 2 +- README.md | 2 +- bin/push-ccp-to-gcr.sh | 2 +- conf/postgres-operator/pgo.yaml | 4 ++-- docs/config.toml | 2 +- examples/create-by-resource/fromcrd.json | 6 +++--- examples/envs.sh | 2 +- examples/helm/README.md | 4 ++-- examples/helm/postgres/Chart.yaml | 2 +- examples/helm/postgres/templates/pgcluster.yaml | 2 +- examples/helm/postgres/values.yaml | 2 +- examples/kustomize/createcluster/README.md | 16 ++++++++-------- .../kustomize/createcluster/base/pgcluster.yaml | 6 +++--- .../overlay/staging/hippo-rpl1-pgreplica.yaml | 2 +- installers/ansible/README.md | 2 +- installers/ansible/values.yaml | 6 +++--- installers/gcp-marketplace/Makefile | 2 +- installers/gcp-marketplace/README.md | 2 +- installers/gcp-marketplace/values.yaml | 6 +++--- installers/helm/Chart.yaml | 2 +- installers/helm/values.yaml | 6 +++--- installers/kubectl/client-setup.sh | 2 +- installers/kubectl/postgres-operator-ocp311.yml | 8 ++++---- installers/kubectl/postgres-operator.yml | 8 ++++---- installers/metrics/ansible/README.md | 2 +- installers/metrics/helm/Chart.yaml | 2 +- installers/metrics/helm/helm_template.yaml | 2 +- installers/metrics/helm/values.yaml | 2 +- .../kubectl/postgres-operator-metrics-ocp311.yml | 2 +- .../kubectl/postgres-operator-metrics.yml | 2 +- installers/olm/Makefile | 2 +- internal/operator/cluster/upgrade.go | 2 +- pkg/apis/crunchydata.com/v1/doc.go | 8 ++++---- pkg/apiservermsgs/common.go | 2 +- redhat/atomic/help.1 | 2 +- redhat/atomic/help.md | 2 +- 36 files changed, 64 insertions(+), 64 deletions(-) diff --git a/Makefile b/Makefile index bf7d3dcabe..4148f80512 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ PGOROOT ?= $(CURDIR) PGO_BASEOS ?= centos8 PGO_IMAGE_PREFIX ?= crunchydata PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) -PGO_VERSION ?= 4.6.3-rc.1 +PGO_VERSION ?= 4.6.3 PGO_PG_VERSION ?= 13 PGO_PG_FULLVERSION ?= 13.3 PGO_BACKREST_VERSION ?= 2.31 diff --git a/README.md b/README.md index 4db69e50b5..a9a679bad9 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ to start as quickly as: ```shell kubectl create namespace pgo -kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.3-rc.1/installers/kubectl/postgres-operator.yml +kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.3/installers/kubectl/postgres-operator.yml ``` Otherwise, we highly recommend following the instructions from our [Quickstart](https://access.crunchydata.com/documentation/postgres-operator/latest/quickstart/). diff --git a/bin/push-ccp-to-gcr.sh b/bin/push-ccp-to-gcr.sh index 46f0c74062..20fb0f9c5a 100755 --- a/bin/push-ccp-to-gcr.sh +++ b/bin/push-ccp-to-gcr.sh @@ -16,7 +16,7 @@ GCR_IMAGE_PREFIX=gcr.io/crunchy-dev-test CCP_IMAGE_PREFIX=crunchydata -CCP_IMAGE_TAG=centos8-13.3-4.6.3-rc.1 +CCP_IMAGE_TAG=centos8-13.3-4.6.3 IMAGES=( crunchy-prometheus diff --git a/conf/postgres-operator/pgo.yaml b/conf/postgres-operator/pgo.yaml index 9d54c59530..b43ccf0661 100644 --- a/conf/postgres-operator/pgo.yaml +++ b/conf/postgres-operator/pgo.yaml @@ -2,7 +2,7 @@ Cluster: CCPImagePrefix: registry.developers.crunchydata.com/crunchydata Metrics: false Badger: false - CCPImageTag: centos8-13.3-4.6.3-rc.1 + CCPImageTag: centos8-13.3-4.6.3 Port: 5432 PGBadgerPort: 10000 ExporterPort: 9187 @@ -80,4 +80,4 @@ Storage: Pgo: Audit: false PGOImagePrefix: registry.developers.crunchydata.com/crunchydata - PGOImageTag: centos8-4.6.3-rc.1 + PGOImageTag: centos8-4.6.3 diff --git a/docs/config.toml b/docs/config.toml index 70e630d6d2..0e5122d6dc 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -25,7 +25,7 @@ disableNavChevron = false # set true to hide next/prev chevron, default is false highlightClientSide = false # set true to use highlight.pack.js instead of the default hugo chroma highlighter menushortcutsnewtab = true # set true to open shortcuts links to a new tab/window enableGitInfo = true -operatorVersion = "4.6.3-rc.1" +operatorVersion = "4.6.3" postgresVersion = "13.3" postgresVersion13 = "13.3" postgresVersion12 = "12.6" diff --git a/examples/create-by-resource/fromcrd.json b/examples/create-by-resource/fromcrd.json index e0a28bb910..5b07ed7f9a 100644 --- a/examples/create-by-resource/fromcrd.json +++ b/examples/create-by-resource/fromcrd.json @@ -10,7 +10,7 @@ "deployment-name": "fromcrd", "name": "fromcrd", "pg-cluster": "fromcrd", - "pgo-version": "4.6.3-rc.1", + "pgo-version": "4.6.3", "pgouser": "pgoadmin" }, "name": "fromcrd", @@ -45,7 +45,7 @@ "supplementalgroups": "" }, "ccpimage": "crunchy-postgres-ha", - "ccpimagetag": "centos8-13.3-4.6.3-rc.1", + "ccpimagetag": "centos8-13.3-4.6.3", "clustername": "fromcrd", "database": "userdb", "exporterport": "9187", @@ -60,7 +60,7 @@ "port": "5432", "user": "testuser", "userlabels": { - "pgo-version": "4.6.3-rc.1" + "pgo-version": "4.6.3" } } } diff --git a/examples/envs.sh b/examples/envs.sh index d9e1b2a923..fe9e178d2c 100644 --- a/examples/envs.sh +++ b/examples/envs.sh @@ -20,7 +20,7 @@ export PGO_CONF_DIR=$PGOROOT/installers/ansible/roles/pgo-operator/files # the version of the Operator you run is set by these vars export PGO_IMAGE_PREFIX=registry.developers.crunchydata.com/crunchydata export PGO_BASEOS=centos8 -export PGO_VERSION=4.6.3-rc.1 +export PGO_VERSION=4.6.3 export PGO_IMAGE_TAG=$PGO_BASEOS-$PGO_VERSION # for setting the pgo apiserver port, disabling TLS or not verifying TLS diff --git a/examples/helm/README.md b/examples/helm/README.md index 235076842a..cec91edba0 100644 --- a/examples/helm/README.md +++ b/examples/helm/README.md @@ -64,7 +64,7 @@ The following values can also be set: - `ha`: Whether or not to deploy a high availability PostgreSQL cluster. Can be either `true` or `false`, defaults to `false`. - `imagePrefix`: The prefix of the container images to use for this PostgreSQL cluster. Default to `registry.developers.crunchydata.com/crunchydata`. - `image`: The name of the container image to use for the PostgreSQL cluster. Defaults to `crunchy-postgres-ha`. -- `imageTag`: The container image tag to use. Defaults to `centos8-13.3-4.6.3-rc.1`. +- `imageTag`: The container image tag to use. Defaults to `centos8-13.3-4.6.3`. - `memory`: The memory limit for the PostgreSQL cluster. Follows standard Kubernetes formatting. - `monitoring`: Whether or not to enable monitoring / metrics collection for this PostgreSQL instance. Can either be `true` or `false`, defaults to `false`. @@ -103,7 +103,7 @@ PGPASSWORD="W4tch0ut4hippo$" psql -h localhost -U hippo hippo ## Notes -Prior to PostgreSQL Operator 4.6.3-rc.1, you will have to manually clean up some of the artifacts when running `helm uninstall`. +Prior to PostgreSQL Operator 4.6.3, you will have to manually clean up some of the artifacts when running `helm uninstall`. ## Additional Resources diff --git a/examples/helm/postgres/Chart.yaml b/examples/helm/postgres/Chart.yaml index d9b60ce061..7b02e573e3 100644 --- a/examples/helm/postgres/Chart.yaml +++ b/examples/helm/postgres/Chart.yaml @@ -20,4 +20,4 @@ version: 0.2.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. -appVersion: 4.6.3-rc.1 +appVersion: 4.6.3 diff --git a/examples/helm/postgres/templates/pgcluster.yaml b/examples/helm/postgres/templates/pgcluster.yaml index a0852741ba..7346e29795 100644 --- a/examples/helm/postgres/templates/pgcluster.yaml +++ b/examples/helm/postgres/templates/pgcluster.yaml @@ -28,7 +28,7 @@ spec: storagetype: dynamic ccpimage: {{ .Values.image | default "crunchy-postgres-ha" | quote }} ccpimageprefix: {{ .Values.imagePrefix | default "registry.developers.crunchydata.com/crunchydata" | quote }} - ccpimagetag: {{ .Values.imageTag | default "centos8-13.3-4.6.3-rc.1" | quote }} + ccpimagetag: {{ .Values.imageTag | default "centos8-13.3-4.6.3" | quote }} clustername: {{ .Values.name | quote }} database: {{ .Values.name | quote }} {{- if .Values.monitoring }} diff --git a/examples/helm/postgres/values.yaml b/examples/helm/postgres/values.yaml index 51c855ed22..6e80fe5ae9 100644 --- a/examples/helm/postgres/values.yaml +++ b/examples/helm/postgres/values.yaml @@ -10,5 +10,5 @@ password: W4tch0ut4hippo$ # ha: true # imagePrefix: registry.developers.crunchydata.com/crunchydata # image: crunchy-postgres-ha -# imageTag: centos8-13.3-4.6.3-rc.1 +# imageTag: centos8-13.3-4.6.3 # memory: 1Gi diff --git a/examples/kustomize/createcluster/README.md b/examples/kustomize/createcluster/README.md index e9c4891193..b399a06bfc 100644 --- a/examples/kustomize/createcluster/README.md +++ b/examples/kustomize/createcluster/README.md @@ -44,13 +44,13 @@ pgo show cluster hippo -n pgo ``` You will see something like this if successful: ``` -cluster : hippo (crunchy-postgres-ha:centos8-13.3-4.6.3-rc.1) +cluster : hippo (crunchy-postgres-ha:centos8-13.3-4.6.3) pod : hippo-8fb6bd96-j87wq (Running) on gke-xxxx-default-pool-38e946bd-257w (1/1) (primary) pvc: hippo (1Gi) deployment : hippo deployment : hippo-backrest-shared-repo service : hippo - ClusterIP (10.0.56.86) - Ports (2022/TCP, 5432/TCP) - labels : pgo-version=4.6.3-rc.1 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.3 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata ``` Feel free to run other pgo cli commands on the hippo cluster @@ -79,7 +79,7 @@ pgo show cluster dev-hippo -n pgo ``` You will see something like this if successful: ``` -cluster : dev-hippo (crunchy-postgres-ha:centos8-13.3-4.6.3-rc.1) +cluster : dev-hippo (crunchy-postgres-ha:centos8-13.3-4.6.3) pod : dev-hippo-588d4cb746-bwrxb (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: dev-hippo (1Gi) deployment : dev-hippo @@ -87,7 +87,7 @@ cluster : dev-hippo (crunchy-postgres-ha:centos8-13.3-4.6.3-rc.1) deployment : dev-hippo-pgbouncer service : dev-hippo - ClusterIP (10.0.62.87) - Ports (2022/TCP, 5432/TCP) service : dev-hippo-pgbouncer - ClusterIP (10.0.48.120) - Ports (5432/TCP) - labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.6.3-rc.1 pgouser=admin + labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.6.3 pgouser=admin ``` #### staging The staging overlay will deploy a crunchy postgreSQL cluster with 2 replica's with annotations added @@ -113,7 +113,7 @@ pgo show cluster staging-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : staging-hippo (crunchy-postgres-ha:centos8-13.3-4.6.3-rc.1) +cluster : staging-hippo (crunchy-postgres-ha:centos8-13.3-4.6.3) pod : staging-hippo-85cf6dcb65-9h748 (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: staging-hippo (1Gi) pod : staging-hippo-lnxw-cf47d8c8b-6r4wn (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (replica) @@ -128,7 +128,7 @@ cluster : staging-hippo (crunchy-postgres-ha:centos8-13.3-4.6.3-rc.1) service : staging-hippo-replica - ClusterIP (10.0.56.57) - Ports (2022/TCP, 5432/TCP) pgreplica : staging-hippo-lnxw pgreplica : staging-hippo-rpl1 - labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.6.3-rc.1 pgouser=admin vendor=crunchydata + labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.6.3 pgouser=admin vendor=crunchydata ``` #### production @@ -154,7 +154,7 @@ pgo show cluster prod-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : prod-hippo (crunchy-postgres-ha:centos8-13.3-4.6.3-rc.1) +cluster : prod-hippo (crunchy-postgres-ha:centos8-13.3-4.6.3) pod : prod-hippo-5d6dd46497-rr67c (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (primary) pvc: prod-hippo (1Gi) pod : prod-hippo-flty-84d97c8769-2pzbh (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (replica) @@ -165,7 +165,7 @@ cluster : prod-hippo (crunchy-postgres-ha:centos8-13.3-4.6.3-rc.1) service : prod-hippo - ClusterIP (10.0.56.18) - Ports (2022/TCP, 5432/TCP) service : prod-hippo-replica - ClusterIP (10.0.56.101) - Ports (2022/TCP, 5432/TCP) pgreplica : prod-hippo-flty - labels : pgo-version=4.6.3-rc.1 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.3 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata ``` ### Delete the clusters To delete the clusters run the following pgo cli commands diff --git a/examples/kustomize/createcluster/base/pgcluster.yaml b/examples/kustomize/createcluster/base/pgcluster.yaml index 0dd69a0148..386e60aeaa 100644 --- a/examples/kustomize/createcluster/base/pgcluster.yaml +++ b/examples/kustomize/createcluster/base/pgcluster.yaml @@ -10,7 +10,7 @@ metadata: deployment-name: hippo name: hippo pg-cluster: hippo - pgo-version: 4.6.3-rc.1 + pgo-version: 4.6.3 pgouser: admin name: hippo namespace: pgo @@ -46,7 +46,7 @@ spec: postgres: {} ccpimage: crunchy-postgres-ha ccpimageprefix: registry.developers.crunchydata.com/crunchydata - ccpimagetag: centos8-13.3-4.6.3-rc.1 + ccpimagetag: centos8-13.3-4.6.3 clustername: hippo customconfig: "" database: hippo @@ -69,4 +69,4 @@ spec: port: "5432" user: hippo userlabels: - pgo-version: 4.6.3-rc.1 + pgo-version: 4.6.3 diff --git a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml index 2867bd4cc1..aa69384539 100644 --- a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml +++ b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml @@ -20,4 +20,4 @@ spec: storagetype: dynamic supplementalgroups: "" userlabels: - pgo-version: 4.6.3-rc.1 + pgo-version: 4.6.3 diff --git a/installers/ansible/README.md b/installers/ansible/README.md index 46ef61c2cc..eddc7044aa 100644 --- a/installers/ansible/README.md +++ b/installers/ansible/README.md @@ -4,7 +4,7 @@ PGO: The Postgres Operator from Crunchy Data

-Latest Release: 4.6.3-rc.1 +Latest Release: 4.6.3 ## General diff --git a/installers/ansible/values.yaml b/installers/ansible/values.yaml index 0d618a1571..509eaf533a 100644 --- a/installers/ansible/values.yaml +++ b/installers/ansible/values.yaml @@ -17,7 +17,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.3-4.6.3-rc.1" +ccp_image_tag: "centos8-13.3-4.6.3" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -49,14 +49,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.3-rc.1" +pgo_client_version: "4.6.3" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos8-4.6.3-rc.1" +pgo_image_tag: "centos8-4.6.3" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/gcp-marketplace/Makefile b/installers/gcp-marketplace/Makefile index 5520eddac3..c86ea556b9 100644 --- a/installers/gcp-marketplace/Makefile +++ b/installers/gcp-marketplace/Makefile @@ -6,7 +6,7 @@ MARKETPLACE_TOOLS ?= gcr.io/cloud-marketplace-tools/k8s/dev:$(MARKETPLACE_VERSIO MARKETPLACE_VERSION ?= 0.9.4 KUBECONFIG ?= $(HOME)/.kube/config PARAMETERS ?= {} -PGO_VERSION ?= 4.6.3-rc.1 +PGO_VERSION ?= 4.6.3 IMAGE_BUILD_ARGS = --build-arg MARKETPLACE_VERSION='$(MARKETPLACE_VERSION)' \ --build-arg PGO_VERSION='$(PGO_VERSION)' diff --git a/installers/gcp-marketplace/README.md b/installers/gcp-marketplace/README.md index 60f31d4e9a..37435c359b 100644 --- a/installers/gcp-marketplace/README.md +++ b/installers/gcp-marketplace/README.md @@ -59,7 +59,7 @@ Google Cloud Marketplace. ```shell IMAGE_REPOSITORY=gcr.io/crunchydata-public/postgres-operator - export PGO_VERSION=4.6.3-rc.1 + export PGO_VERSION=4.6.3 export INSTALLER_IMAGE=${IMAGE_REPOSITORY}/deployer:${PGO_VERSION} export OPERATOR_IMAGE=${IMAGE_REPOSITORY}:${PGO_VERSION} export OPERATOR_IMAGE_API=${IMAGE_REPOSITORY}/pgo-apiserver:${PGO_VERSION} diff --git a/installers/gcp-marketplace/values.yaml b/installers/gcp-marketplace/values.yaml index 5f20281ecc..24c99783a6 100644 --- a/installers/gcp-marketplace/values.yaml +++ b/installers/gcp-marketplace/values.yaml @@ -10,7 +10,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.3-4.6.3-rc.1" +ccp_image_tag: "centos8-13.3-4.6.3" create_rbac: "true" db_name: "" db_password_age_days: "0" @@ -32,9 +32,9 @@ pgo_admin_role_name: "pgoadmin" pgo_admin_username: "admin" pgo_client_container_install: "false" pgo_client_install: 'false' -pgo_client_version: "4.6.3-rc.1" +pgo_client_version: "4.6.3" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.3-rc.1" +pgo_image_tag: "centos8-4.6.3" pgo_installation_name: '${OPERATOR_NAME}' pgo_operator_namespace: '${OPERATOR_NAMESPACE}' scheduler_timeout: "3600" diff --git a/installers/helm/Chart.yaml b/installers/helm/Chart.yaml index 7ac33ac604..24610041b0 100644 --- a/installers/helm/Chart.yaml +++ b/installers/helm/Chart.yaml @@ -3,7 +3,7 @@ name: postgres-operator description: 'PGO: The Postgres Operator from Crunchy Data Helm Chart for Kubernetes' type: application version: 0.2.0 -appVersion: 4.6.3-rc.1 +appVersion: 4.6.3 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/docs/static/logos/pgo.svg keywords: diff --git a/installers/helm/values.yaml b/installers/helm/values.yaml index 2ecc55cd6a..c1fbbe5332 100644 --- a/installers/helm/values.yaml +++ b/installers/helm/values.yaml @@ -37,7 +37,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.3-4.6.3-rc.1" +ccp_image_tag: "centos8-13.3-4.6.3" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -69,14 +69,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.3-rc.1" +pgo_client_version: "4.6.3" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos8-4.6.3-rc.1" +pgo_image_tag: "centos8-4.6.3" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/kubectl/client-setup.sh b/installers/kubectl/client-setup.sh index 7f14dd18df..aac97906c7 100755 --- a/installers/kubectl/client-setup.sh +++ b/installers/kubectl/client-setup.sh @@ -14,7 +14,7 @@ # This script should be run after the operator has been deployed PGO_OPERATOR_NAMESPACE="${PGO_OPERATOR_NAMESPACE:-pgo}" PGO_USER_ADMIN="${PGO_USER_ADMIN:-pgouser-admin}" -PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.3-rc.1}" +PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.3}" PGO_CLIENT_URL="https://github.com/CrunchyData/postgres-operator/releases/download/${PGO_CLIENT_VERSION}" PGO_CMD="${PGO_CMD-kubectl}" diff --git a/installers/kubectl/postgres-operator-ocp311.yml b/installers/kubectl/postgres-operator-ocp311.yml index e1250e5fd5..d19d9ffec1 100644 --- a/installers/kubectl/postgres-operator-ocp311.yml +++ b/installers/kubectl/postgres-operator-ocp311.yml @@ -44,7 +44,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos8-13.3-4.6.3-rc.1" + ccp_image_tag: "centos8-13.3-4.6.3" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -77,14 +77,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.3-rc.1" + pgo_client_version: "4.6.3" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos8-4.6.3-rc.1" + pgo_image_tag: "centos8-4.6.3" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -161,7 +161,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.3-rc.1 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.3 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/kubectl/postgres-operator.yml b/installers/kubectl/postgres-operator.yml index ee621ef840..2dabf8e782 100644 --- a/installers/kubectl/postgres-operator.yml +++ b/installers/kubectl/postgres-operator.yml @@ -139,7 +139,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos8-13.3-4.6.3-rc.1" + ccp_image_tag: "centos8-13.3-4.6.3" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -172,14 +172,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.3-rc.1" + pgo_client_version: "4.6.3" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos8-4.6.3-rc.1" + pgo_image_tag: "centos8-4.6.3" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -269,7 +269,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.3-rc.1 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.3 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/ansible/README.md b/installers/metrics/ansible/README.md index d3c5e0a349..2dd03f551c 100644 --- a/installers/metrics/ansible/README.md +++ b/installers/metrics/ansible/README.md @@ -4,7 +4,7 @@ Crunchy Data

-Latest Release: 4.6.3-rc.1 +Latest Release: 4.6.3 ## General diff --git a/installers/metrics/helm/Chart.yaml b/installers/metrics/helm/Chart.yaml index af687070cf..a04a002b04 100644 --- a/installers/metrics/helm/Chart.yaml +++ b/installers/metrics/helm/Chart.yaml @@ -3,6 +3,6 @@ name: postgres-operator-monitoring description: Install for Crunchy PostgreSQL Operator Monitoring type: application version: 0.2.0 -appVersion: 4.6.3-rc.1 +appVersion: 4.6.3 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/docs/static/logos/pgo.svg diff --git a/installers/metrics/helm/helm_template.yaml b/installers/metrics/helm/helm_template.yaml index 0baff5e0f0..cf78dfc1e3 100644 --- a/installers/metrics/helm/helm_template.yaml +++ b/installers/metrics/helm/helm_template.yaml @@ -20,5 +20,5 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.3-rc.1" +pgo_image_tag: "centos8-4.6.3" diff --git a/installers/metrics/helm/values.yaml b/installers/metrics/helm/values.yaml index f1833db755..a39df1cf6c 100644 --- a/installers/metrics/helm/values.yaml +++ b/installers/metrics/helm/values.yaml @@ -20,7 +20,7 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.3-rc.1" +pgo_image_tag: "centos8-4.6.3" # ===================== # Configuration Options diff --git a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml index 953a9777cb..0b9873951c 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml @@ -96,7 +96,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.3-rc.1 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.3 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/kubectl/postgres-operator-metrics.yml b/installers/metrics/kubectl/postgres-operator-metrics.yml index 953f778ace..9b646e0033 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics.yml @@ -165,7 +165,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.3-rc.1 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.3 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/olm/Makefile b/installers/olm/Makefile index 955caec5d5..b725a2b669 100644 --- a/installers/olm/Makefile +++ b/installers/olm/Makefile @@ -11,7 +11,7 @@ OLM_TOOLS ?= registry.localhost:5000/postgres-operator-olm-tools:$(OLM_SDK_VERSI OLM_VERSION ?= 0.15.1 PGO_BASEOS ?= centos8 PGO_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -PGO_VERSION ?= 4.6.3-rc.1 +PGO_VERSION ?= 4.6.3 PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) CCP_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(PGO_VERSION) CCP_POSTGIS_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(CCP_POSTGIS_VERSION)-$(PGO_VERSION) diff --git a/internal/operator/cluster/upgrade.go b/internal/operator/cluster/upgrade.go index 1c4ddd5638..ccf2f4fa32 100644 --- a/internal/operator/cluster/upgrade.go +++ b/internal/operator/cluster/upgrade.go @@ -953,7 +953,7 @@ func updatePGBackRestSSHDConfig(clientset kubernetes.Interface, repoSecret *v1.S ctx := context.TODO() updatedRepoSecret := repoSecret.DeepCopy() - // For versions prior to v4.6.3-rc.1, the UsePAM setting might be set to 'yes' as previously + // For versions prior to v4.6.3, the UsePAM setting might be set to 'yes' as previously // required to workaround a known Docker issue. Since this issue has since been resolved, // we now want to ensure this setting is set to 'no'. if !usePAMRegex.MatchString(string(updatedRepoSecret.Data["sshd_config"])) { diff --git a/pkg/apis/crunchydata.com/v1/doc.go b/pkg/apis/crunchydata.com/v1/doc.go index 8547301d36..88b11f6742 100644 --- a/pkg/apis/crunchydata.com/v1/doc.go +++ b/pkg/apis/crunchydata.com/v1/doc.go @@ -53,7 +53,7 @@ cluster. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.3-rc.1", + '{"ClientVersion":"4.6.3", "Namespace":"pgouser1", "Name":"mycluster", $PGO_APISERVER_URL/clusters @@ -72,7 +72,7 @@ show all of the clusters that are in the given namespace. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.3-rc.1", + '{"ClientVersion":"4.6.3", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/showclusters @@ -82,7 +82,7 @@ $PGO_APISERVER_URL/showclusters curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.3-rc.1", + '{"ClientVersion":"4.6.3", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/clustersdelete @@ -90,7 +90,7 @@ $PGO_APISERVER_URL/clustersdelete Schemes: http, https BasePath: / - Version: 4.6.3-rc.1 + Version: 4.6.3 License: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0 Contact: Crunchy Data https://www.crunchydata.com/ diff --git a/pkg/apiservermsgs/common.go b/pkg/apiservermsgs/common.go index 879049694f..a548c42272 100644 --- a/pkg/apiservermsgs/common.go +++ b/pkg/apiservermsgs/common.go @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -const PGO_VERSION = "4.6.3-rc.1" +const PGO_VERSION = "4.6.3" // Ok status const Ok = "ok" diff --git a/redhat/atomic/help.1 b/redhat/atomic/help.1 index e412118618..49b47b99fd 100644 --- a/redhat/atomic/help.1 +++ b/redhat/atomic/help.1 @@ -56,4 +56,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa \fB\fCRelease=\fR .PP -The specific release number of the container. For example, Release="4.6.3-rc.1" +The specific release number of the container. For example, Release="4.6.3" diff --git a/redhat/atomic/help.md b/redhat/atomic/help.md index 5eebd59621..c829251582 100644 --- a/redhat/atomic/help.md +++ b/redhat/atomic/help.md @@ -45,4 +45,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa `Release=` -The specific release number of the container. For example, Release="4.6.3-rc.1" +The specific release number of the container. For example, Release="4.6.3" From bb48a9ae6bdc80a1a0ddf3bf0d7eee55423e6b96 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 24 May 2021 16:08:28 -0400 Subject: [PATCH 233/276] Add release date for 4.6.3 release --- docs/content/releases/4.6.3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/releases/4.6.3.md b/docs/content/releases/4.6.3.md index 652ce4fc7a..be402ffbaa 100644 --- a/docs/content/releases/4.6.3.md +++ b/docs/content/releases/4.6.3.md @@ -5,7 +5,7 @@ draft: false weight: 57 --- -Crunchy Data announces the release of the PostgreSQL Operator 4.6.3 on May DD, 2021. +Crunchy Data announces the release of the PostgreSQL Operator 4.6.3 on May 25, 2021. The PostgreSQL Operator is released in conjunction with the [Crunchy Container Suite](https://github.com/CrunchyData/crunchy-containers/). From e1809939e84ff50e9895c383a50272535f1178a8 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 24 May 2021 21:15:17 -0400 Subject: [PATCH 234/276] Ensure superfluous label is removed from user labels on upgrade This removes the "pg-pod-anti-affinity" label from the "userlabels" section of the pgclusters custom resource during the upgrade process. This has long been managed through the custom resource itself and should not be present within the custom labels. Issue: [ch11573] --- internal/operator/cluster/upgrade.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/operator/cluster/upgrade.go b/internal/operator/cluster/upgrade.go index ccf2f4fa32..f12c1ed7d8 100644 --- a/internal/operator/cluster/upgrade.go +++ b/internal/operator/cluster/upgrade.go @@ -541,6 +541,11 @@ func preparePgclusterForUpgrade(pgcluster *crv1.Pgcluster, parameters map[string } delete(pgcluster.Spec.UserLabels, "service-type") + // 4.6.0 removed the "pg-pod-anti-affinity" label from user labels, as this is + // superfluous and handled through other processes. We can explicitly + // eliminate it + delete(pgcluster.Spec.UserLabels, "pg-pod-anti-affinity") + // 4.6.0 moved the "autofail" label to the DisableAutofail attribute. Given // by default we need to start in an autofailover state, we just delete the // legacy attribute From 404f6b45d0e0cfeb525a5ed4f32d82847c9d1f88 Mon Sep 17 00:00:00 2001 From: jmckulk Date: Thu, 8 Jul 2021 16:48:02 -0400 Subject: [PATCH 235/276] Update label for policy The policy logic uses the service name and role label to determine which pod is the primary and where to run SQL. This change uses the pg-cluster label instead of the service name label. --- internal/apiserver/policyservice/policyimpl.go | 6 +++--- internal/util/policy.go | 9 ++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/internal/apiserver/policyservice/policyimpl.go b/internal/apiserver/policyservice/policyimpl.go index 59808d925a..e06da11b83 100644 --- a/internal/apiserver/policyservice/policyimpl.go +++ b/internal/apiserver/policyservice/policyimpl.go @@ -209,7 +209,7 @@ func ApplyPolicy(request *msgs.ApplyPolicyRequest, ns, pgouser string) msgs.Appl if request.DryRun { for _, d := range allDeployments { - log.Debugf("deployment : %s", d.ObjectMeta.Name) + log.Debugf("deployment: %s", d.ObjectMeta.Name) resp.Name = append(resp.Name, d.ObjectMeta.Name) } return resp @@ -236,7 +236,7 @@ func ApplyPolicy(request *msgs.ApplyPolicyRequest, ns, pgouser string) msgs.Appl cl, err := apiserver.Clientset. CrunchydataV1().Pgclusters(ns). - Get(ctx, d.ObjectMeta.Labels[config.LABEL_SERVICE_NAME], metav1.GetOptions{}) + Get(ctx, d.ObjectMeta.Labels[config.LABEL_PG_CLUSTER], metav1.GetOptions{}) if err != nil { resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() @@ -244,7 +244,7 @@ func ApplyPolicy(request *msgs.ApplyPolicyRequest, ns, pgouser string) msgs.Appl } if err := util.ExecPolicy(apiserver.Clientset, apiserver.RESTConfig, - ns, request.Name, d.ObjectMeta.Labels[config.LABEL_SERVICE_NAME], cl.Spec.Port); err != nil { + ns, request.Name, d.ObjectMeta.Labels[config.LABEL_PG_CLUSTER], cl.Spec.Port); err != nil { log.Error(err) resp.Status.Code = msgs.Error resp.Status.Msg = err.Error() diff --git a/internal/util/policy.go b/internal/util/policy.go index 5fe260ffef..567623e21b 100644 --- a/internal/util/policy.go +++ b/internal/util/policy.go @@ -32,7 +32,7 @@ import ( ) // ExecPolicy execute a sql policy against a cluster -func ExecPolicy(clientset kubeapi.Interface, restconfig *rest.Config, namespace, policyName, serviceName, port string) error { +func ExecPolicy(clientset kubeapi.Interface, restconfig *rest.Config, namespace, policyName, clusterName, port string) error { ctx := context.TODO() // fetch the policy sql @@ -46,11 +46,10 @@ func ExecPolicy(clientset kubeapi.Interface, restconfig *rest.Config, namespace, stdin := strings.NewReader(sql) // now, we need to ensure we can get the Pod name of the primary PostgreSQL - // instance. Thname being passed in is actually the "serviceName" of the Pod - // We can isolate the exact Pod we want by using this (LABEL_SERVICE_NAME) and - // the LABEL_PGHA_ROLE labels + // instance. We can isolate the exact Pod we want by using the + // LABEL_PG_CLUSTER and LABEL_PGHA_ROLE labels selector := fmt.Sprintf("%s=%s,%s=%s", - config.LABEL_SERVICE_NAME, serviceName, + config.LABEL_PG_CLUSTER, clusterName, config.LABEL_PGHA_ROLE, config.LABEL_PGHA_ROLE_PRIMARY) podList, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{LabelSelector: selector}) From 93025df586a1129406385a11f84e3debcdd675c4 Mon Sep 17 00:00:00 2001 From: TJ Moore Date: Fri, 9 Jul 2021 15:28:58 -0400 Subject: [PATCH 236/276] Allow backup config change when recreated cluster Previously, when attempting to change the backup configuration on an existing cluster, stanza-create job will error out due to missing S3 configuration parameters despite being provided as part of the pgo create cluster command. This update corrects that issue by ensuring the existing pgBackRest repo config secret is updated to include the necessary configuration values. --- .../apiserver/clusterservice/clusterimpl.go | 97 +++++++++++++------ 1 file changed, 70 insertions(+), 27 deletions(-) diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index dc9542aff3..13f89419c1 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -929,30 +929,41 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. // the deployment template always tries to mount /sshd volume secretName := fmt.Sprintf("%s-%s", clusterName, config.LABEL_BACKREST_REPO_SECRET) - if _, err := apiserver.Clientset. - CoreV1().Secrets(request.Namespace). - Get(ctx, secretName, metav1.GetOptions{}); kubeapi.IsNotFound(err) { - // determine if a custom CA secret should be used - backrestS3CACert := []byte{} - - if request.BackrestS3CASecretName != "" { - backrestSecret, err := apiserver.Clientset. - CoreV1().Secrets(request.Namespace). - Get(ctx, request.BackrestS3CASecretName, metav1.GetOptions{}) - if err != nil { - log.Error(err) - resp.Status.Code = msgs.Error - resp.Status.Msg = fmt.Sprintf("Error finding pgBackRest S3 CA secret \"%s\": %s", - request.BackrestS3CASecretName, err.Error()) - return resp - } + // determine if a custom CA secret should be used + backrestS3CACert := []byte{} - // attempt to retrieves the custom CA, assuming it has the name - // "aws-s3-ca.crt" - backrestS3CACert = backrestSecret.Data[util.BackRestRepoSecretKeyAWSS3KeyAWSS3CACert] + if request.BackrestS3CASecretName != "" { + backrestSecret, err := apiserver.Clientset. + CoreV1().Secrets(request.Namespace). + Get(ctx, request.BackrestS3CASecretName, metav1.GetOptions{}) + if err != nil { + log.Error(err) + resp.Status.Code = msgs.Error + resp.Status.Msg = fmt.Sprintf("Error finding pgBackRest S3 CA secret \"%s\": %s", + request.BackrestS3CASecretName, err.Error()) + return resp } - // set up the secret for the cluster that contains the pgBackRest + // attempt to retrieves the custom CA, assuming it has the name + // "aws-s3-ca.crt" + backrestS3CACert = backrestSecret.Data[util.BackRestRepoSecretKeyAWSS3KeyAWSS3CACert] + } + + // save the S3 credentials in a single map so it can be used to either create a new + // secret or update an existing one + s3Credentials := map[string][]byte{ + util.BackRestRepoSecretKeyAWSS3KeyAWSS3CACert: backrestS3CACert, + util.BackRestRepoSecretKeyAWSS3KeyAWSS3Key: []byte(request.BackrestS3Key), + util.BackRestRepoSecretKeyAWSS3KeyAWSS3KeySecret: []byte(request.BackrestS3KeySecret), + } + + _, err = apiserver.Clientset.CoreV1().Secrets(request.Namespace). + Get(ctx, secretName, metav1.GetOptions{}) + + switch { + case kubeapi.IsNotFound(err): + // The pgBackRest repo config secret was not found, create it. + // Set up the secret for the cluster that contains the pgBackRest // information secret := &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ @@ -963,11 +974,7 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. config.LABEL_PGO_BACKREST_REPO: "true", }, }, - Data: map[string][]byte{ - util.BackRestRepoSecretKeyAWSS3KeyAWSS3CACert: backrestS3CACert, - util.BackRestRepoSecretKeyAWSS3KeyAWSS3Key: []byte(request.BackrestS3Key), - util.BackRestRepoSecretKeyAWSS3KeyAWSS3KeySecret: []byte(request.BackrestS3KeySecret), - }, + Data: s3Credentials, } if _, err := apiserver.Clientset.CoreV1().Secrets(ns).Create(ctx, secret, metav1.CreateOptions{}); err != nil && !kubeapi.IsAlreadyExists(err) { @@ -975,10 +982,22 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. resp.Status.Msg = fmt.Sprintf("could not create backrest repo secret: %s", err) return resp } - } else if err != nil { + + case err != nil: + // An error occurred other than 'not found'. Log the error received when + // attempting to get the pgBackRest repo config secret, then return. resp.Status.Code = msgs.Error resp.Status.Msg = fmt.Sprintf("could not query if backrest repo secret exits: %s", err) return resp + default: + // the pgBackRest repo config secret already exists, update any provided + // S3 credential information + err = updateRepoSecret(apiserver.Clientset, secretName, request.Namespace, s3Credentials) + if err != nil { + resp.Status.Code = msgs.Error + resp.Status.Msg = fmt.Sprintf("could not update backrest repo secret: %s", err) + return resp + } } // create a workflow for this new cluster @@ -1011,6 +1030,30 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns, pgouser string) msgs. return resp } +// updateRepoSecret updates the existing pgBackRest repo config secret with any +// provided S3/GCS connection information. +func updateRepoSecret(clientset kubernetes.Interface, secretName, + namespace string, connectionInfo map[string][]byte) error { + ctx := context.TODO() + + // Get the secret + secret, err := clientset.CoreV1().Secrets(namespace). + Get(ctx, secretName, metav1.GetOptions{}) + // The secret should already exist at this point. If there is any error, + // return. + if err != nil { + return err + } + + // update the secret data + for k, v := range connectionInfo { + secret.Data[k] = v + } + _, err = clientset.CoreV1().Secrets(secret.Namespace).Update(ctx, secret, + metav1.UpdateOptions{}) + return err +} + func validateConfigPolicies(clusterName, PoliciesFlag, ns string) error { ctx := context.TODO() var err error From bbfe66acf950ba48d20ad07bc4ba9cf3a9eaefc1 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Sun, 30 May 2021 12:39:31 -0400 Subject: [PATCH 237/276] Ensure "vendor" label is present on PGO objects Though PGO leverages a lot of stable naming, this label is a helpful identifier among the objects that PGO is managing. Some derived objects (e.g. Secrets from SAs) do not get the label, but they are tightly coulpled to the object that does get the label. Most objects had the "vendor" label already, but this should bring everything inline. Issue: #2470 --- deploy/cluster-role-bindings.yaml | 2 ++ deploy/cluster-roles-readonly.yaml | 2 ++ deploy/cluster-roles.yaml | 2 ++ deploy/deploy.sh | 5 ++++- deploy/local-namespace-rbac.yaml | 6 ++++++ deploy/role-bindings.yaml | 2 ++ deploy/roles.yaml | 2 ++ deploy/service-accounts.yaml | 2 ++ deploy/service.json | 3 ++- docs/content/installation/other/operator-hub.md | 2 ++ .../pgo-operator/files/crds/pgclusters-crd.yaml | 2 ++ .../pgo-operator/files/crds/pgpolicies-crd.yaml | 2 ++ .../pgo-operator/files/crds/pgreplicas-crd.yaml | 2 ++ .../roles/pgo-operator/files/crds/pgtasks-crd.yaml | 2 ++ .../files/pgo-configs/pgo-backrest-role-binding.json | 5 ++++- .../files/pgo-configs/pgo-backrest-role.json | 5 ++++- .../files/pgo-configs/pgo-backrest-sa.json | 5 ++++- .../files/pgo-configs/pgo-default-sa.json | 5 ++++- .../files/pgo-configs/pgo-target-role-binding.json | 5 ++++- .../files/pgo-configs/pgo-target-role.json | 5 ++++- .../files/pgo-configs/pgo-target-sa.json | 5 ++++- installers/ansible/roles/pgo-operator/tasks/main.yml | 3 ++- .../templates/cluster-rbac-readonly.yaml.j2 | 4 ++++ .../pgo-operator/templates/cluster-rbac.yaml.j2 | 4 ++++ .../templates/local-namespace-rbac.yaml.j2 | 6 ++++++ .../pgo-operator/templates/pgo-role-rbac.yaml.j2 | 4 ++++ .../templates/pgo-service-account.yaml.j2 | 2 ++ .../roles/pgo-operator/templates/service.json.j2 | 3 ++- installers/kubectl/postgres-operator-ocp311.yml | 10 ++++++++++ installers/kubectl/postgres-operator.yml | 12 ++++++++++++ .../kubectl/postgres-operator-metrics-ocp311.yml | 5 +++++ .../metrics/kubectl/postgres-operator-metrics.yml | 6 ++++++ installers/olm/description.openshift.md | 1 + installers/olm/description.upstream.md | 1 + installers/olm/postgresoperator.crd.yaml | 8 ++++++++ internal/config/pgoconfig.go | 3 +++ internal/operator/common.go | 3 +++ 37 files changed, 135 insertions(+), 11 deletions(-) diff --git a/deploy/cluster-role-bindings.yaml b/deploy/cluster-role-bindings.yaml index be7d75bb2f..1f6f9a2b35 100644 --- a/deploy/cluster-role-bindings.yaml +++ b/deploy/cluster-role-bindings.yaml @@ -3,6 +3,8 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: pgo-cluster-role + labels: + vendor: crunchydata roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole diff --git a/deploy/cluster-roles-readonly.yaml b/deploy/cluster-roles-readonly.yaml index 773e6cd07e..900e83d434 100644 --- a/deploy/cluster-roles-readonly.yaml +++ b/deploy/cluster-roles-readonly.yaml @@ -2,6 +2,8 @@ kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: pgo-cluster-role + labels: + vendor: crunchydata rules: - apiGroups: - '' diff --git a/deploy/cluster-roles.yaml b/deploy/cluster-roles.yaml index d760492836..e2a90137c4 100644 --- a/deploy/cluster-roles.yaml +++ b/deploy/cluster-roles.yaml @@ -3,6 +3,8 @@ kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: pgo-cluster-role + labels: + vendor: crunchydata rules: - apiGroups: - '' diff --git a/deploy/deploy.sh b/deploy/deploy.sh index ae8b1b3eea..34cb7332a4 100755 --- a/deploy/deploy.sh +++ b/deploy/deploy.sh @@ -51,6 +51,8 @@ then $PGO_CMD --namespace=$PGO_OPERATOR_NAMESPACE create secret generic pgo-backrest-repo-config \ --from-literal=aws-s3-key="${pgbackrest_aws_s3_key}" \ --from-literal=aws-s3-key-secret="${pgbackrest_aws_s3_key_secret}" + $PGO_CMD --namespace=$PGO_OPERATOR_NAMESPACE label secret pgo-backrest-repo-config \ + vendor=crunchydata fi # @@ -63,11 +65,12 @@ then fi $PGO_CMD --namespace=$PGO_OPERATOR_NAMESPACE create secret tls pgo.tls --key=${PGOROOT}/conf/postgres-operator/server.key --cert=${PGOROOT}/conf/postgres-operator/server.crt +$PGO_CMD --namespace=$PGO_OPERATOR_NAMESPACE label secret pgo.tls vendor=crunchydata $PGO_CMD --namespace=$PGO_OPERATOR_NAMESPACE create configmap pgo-config \ --from-file=${PGOROOT}/conf/postgres-operator/pgo.yaml \ --from-file=${PGO_CONF_DIR}/pgo-configs - +$PGO_CMD --namespace=$PGO_OPERATOR_NAMESPACE label configmap pgo-config vendor=crunchydata # # check if custom port value is set, otherwise set default values diff --git a/deploy/local-namespace-rbac.yaml b/deploy/local-namespace-rbac.yaml index d74f947653..29277675c6 100644 --- a/deploy/local-namespace-rbac.yaml +++ b/deploy/local-namespace-rbac.yaml @@ -3,6 +3,8 @@ kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: name: pgo-local-ns + labels: + vendor: crunchydata rules: - apiGroups: - '' @@ -28,6 +30,8 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: pgo-local-ns + labels: + vendor: crunchydata roleRef: apiGroup: rbac.authorization.k8s.io kind: Role @@ -41,6 +45,8 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: pgo-target-role-binding + labels: + vendor: crunchydata roleRef: apiGroup: rbac.authorization.k8s.io kind: Role diff --git a/deploy/role-bindings.yaml b/deploy/role-bindings.yaml index b8f21c2391..916858e8ed 100644 --- a/deploy/role-bindings.yaml +++ b/deploy/role-bindings.yaml @@ -4,6 +4,8 @@ kind: RoleBinding metadata: name: pgo-role namespace: "$PGO_OPERATOR_NAMESPACE" + labels: + vendor: crunchydata roleRef: apiGroup: rbac.authorization.k8s.io kind: Role diff --git a/deploy/roles.yaml b/deploy/roles.yaml index 899551f6a1..e800165f15 100644 --- a/deploy/roles.yaml +++ b/deploy/roles.yaml @@ -4,6 +4,8 @@ apiVersion: rbac.authorization.k8s.io/v1 metadata: name: pgo-role namespace: "$PGO_OPERATOR_NAMESPACE" + labels: + vendor: crunchydata rules: - apiGroups: - '' diff --git a/deploy/service-accounts.yaml b/deploy/service-accounts.yaml index f631c8e06b..d48909d647 100644 --- a/deploy/service-accounts.yaml +++ b/deploy/service-accounts.yaml @@ -4,3 +4,5 @@ kind: ServiceAccount metadata: name: postgres-operator namespace: $PGO_OPERATOR_NAMESPACE + labels: + vendor: crunchydata diff --git a/deploy/service.json b/deploy/service.json index f026f5d7d5..97b76e4bc9 100644 --- a/deploy/service.json +++ b/deploy/service.json @@ -4,7 +4,8 @@ "metadata": { "name": "postgres-operator", "labels": { - "name": "postgres-operator" + "name": "postgres-operator", + "vendor": "crunchydata" } }, "spec": { diff --git a/docs/content/installation/other/operator-hub.md b/docs/content/installation/other/operator-hub.md index 6495c53910..38db177892 100644 --- a/docs/content/installation/other/operator-hub.md +++ b/docs/content/installation/other/operator-hub.md @@ -25,6 +25,8 @@ If you plan to use AWS S3 to store backups and would like to have the keys avail kubectl -n "$PGO_OPERATOR_NAMESPACE" create secret generic pgo-backrest-repo-config \ --from-literal=aws-s3-key="" \ --from-literal=aws-s3-key-secret="" +kubectl -n "$PGO_OPERATOR_NAMESPACE" label secret pgo-backrest-repo-config \ + vendor=crunchydata ``` ### Certificates (optional) diff --git a/installers/ansible/roles/pgo-operator/files/crds/pgclusters-crd.yaml b/installers/ansible/roles/pgo-operator/files/crds/pgclusters-crd.yaml index 92c9fd7cd5..86c19ccdc3 100644 --- a/installers/ansible/roles/pgo-operator/files/crds/pgclusters-crd.yaml +++ b/installers/ansible/roles/pgo-operator/files/crds/pgclusters-crd.yaml @@ -3,6 +3,8 @@ apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: pgclusters.crunchydata.com + labels: + vendor: crunchydata spec: group: crunchydata.com names: diff --git a/installers/ansible/roles/pgo-operator/files/crds/pgpolicies-crd.yaml b/installers/ansible/roles/pgo-operator/files/crds/pgpolicies-crd.yaml index 32e0d2014c..73d84f0173 100644 --- a/installers/ansible/roles/pgo-operator/files/crds/pgpolicies-crd.yaml +++ b/installers/ansible/roles/pgo-operator/files/crds/pgpolicies-crd.yaml @@ -3,6 +3,8 @@ apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: pgpolicies.crunchydata.com + labels: + vendor: crunchydata spec: group: crunchydata.com names: diff --git a/installers/ansible/roles/pgo-operator/files/crds/pgreplicas-crd.yaml b/installers/ansible/roles/pgo-operator/files/crds/pgreplicas-crd.yaml index 303f77f1ce..167474a41f 100644 --- a/installers/ansible/roles/pgo-operator/files/crds/pgreplicas-crd.yaml +++ b/installers/ansible/roles/pgo-operator/files/crds/pgreplicas-crd.yaml @@ -3,6 +3,8 @@ apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: pgreplicas.crunchydata.com + labels: + vendor: crunchydata spec: group: crunchydata.com names: diff --git a/installers/ansible/roles/pgo-operator/files/crds/pgtasks-crd.yaml b/installers/ansible/roles/pgo-operator/files/crds/pgtasks-crd.yaml index 20fce21e7a..14ae07386d 100644 --- a/installers/ansible/roles/pgo-operator/files/crds/pgtasks-crd.yaml +++ b/installers/ansible/roles/pgo-operator/files/crds/pgtasks-crd.yaml @@ -3,6 +3,8 @@ apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: pgtasks.crunchydata.com + labels: + vendor: crunchydata spec: group: crunchydata.com names: diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-role-binding.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-role-binding.json index 84f1c031fc..5c4163b892 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-role-binding.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-role-binding.json @@ -3,7 +3,10 @@ "kind": "RoleBinding", "metadata": { "name": "pgo-backrest-role-binding", - "namespace": "{{.TargetNamespace}}" + "namespace": "{{.TargetNamespace}}", + "labels": { + "vendor": "crunchydata" + } }, "roleRef": { "apiGroup": "rbac.authorization.k8s.io", diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-role.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-role.json index ca1c5b4e0b..f14634c7c1 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-role.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-role.json @@ -3,7 +3,10 @@ "kind": "Role", "metadata": { "name": "pgo-backrest-role", - "namespace": "{{.TargetNamespace}}" + "namespace": "{{.TargetNamespace}}", + "labels": { + "vendor": "crunchydata" + } }, "rules": [ { diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-sa.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-sa.json index d3d8d19c4b..cf5607a504 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-sa.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-backrest-sa.json @@ -3,6 +3,9 @@ "kind": "ServiceAccount", "metadata": { "name": "pgo-backrest", - "namespace": "{{.TargetNamespace}}" + "namespace": "{{.TargetNamespace}}", + "labels": { + "vendor": "crunchydata" + } } } diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-default-sa.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-default-sa.json index 5a8a52865c..f35dd542bd 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-default-sa.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-default-sa.json @@ -3,7 +3,10 @@ "kind": "ServiceAccount", "metadata": { "name": "pgo-default", - "namespace": "{{.TargetNamespace}}" + "namespace": "{{.TargetNamespace}}", + "labels": { + "vendor": "crunchydata" + } }, "automountServiceAccountToken": false } diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-target-role-binding.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-target-role-binding.json index df279ee347..5b23bcd927 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-target-role-binding.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-target-role-binding.json @@ -3,7 +3,10 @@ "kind": "RoleBinding", "metadata": { "name": "pgo-target-role-binding", - "namespace": "{{.TargetNamespace}}" + "namespace": "{{.TargetNamespace}}", + "labels": { + "vendor": "crunchydata" + } }, "roleRef": { "apiGroup": "rbac.authorization.k8s.io", diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-target-role.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-target-role.json index 09b77ef469..612307356d 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-target-role.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-target-role.json @@ -3,7 +3,10 @@ "kind": "Role", "metadata": { "name": "pgo-target-role", - "namespace": "{{.TargetNamespace}}" + "namespace": "{{.TargetNamespace}}", + "labels": { + "vendor": "crunchydata" + } }, "rules": [ { diff --git a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-target-sa.json b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-target-sa.json index 5d31bd4441..28cfb06565 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-target-sa.json +++ b/installers/ansible/roles/pgo-operator/files/pgo-configs/pgo-target-sa.json @@ -3,6 +3,9 @@ "kind": "ServiceAccount", "metadata": { "name": "pgo-target", - "namespace": "{{.TargetNamespace}}" + "namespace": "{{.TargetNamespace}}", + "labels": { + "vendor": "crunchydata" + } } } diff --git a/installers/ansible/roles/pgo-operator/tasks/main.yml b/installers/ansible/roles/pgo-operator/tasks/main.yml index a19e3c7b12..205c7385ed 100644 --- a/installers/ansible/roles/pgo-operator/tasks/main.yml +++ b/installers/ansible/roles/pgo-operator/tasks/main.yml @@ -215,7 +215,8 @@ command: | {{ kubectl_or_oc }} create clusterrolebinding pgo-cluster-admin \ --clusterrole cluster-admin \ - --serviceaccount "{{ pgo_operator_namespace }}:postgres-operator" + --serviceaccount "{{ pgo_operator_namespace }}:postgres-operator" && \ + {{ kubectl_or_oc }} label clusterrolebinding pgo-cluster-admin vendor=crunchydata when: pgo_cluster_admin_result.rc == 1 diff --git a/installers/ansible/roles/pgo-operator/templates/cluster-rbac-readonly.yaml.j2 b/installers/ansible/roles/pgo-operator/templates/cluster-rbac-readonly.yaml.j2 index 3021d4a058..f34e1b9579 100644 --- a/installers/ansible/roles/pgo-operator/templates/cluster-rbac-readonly.yaml.j2 +++ b/installers/ansible/roles/pgo-operator/templates/cluster-rbac-readonly.yaml.j2 @@ -3,6 +3,8 @@ kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: pgo-cluster-role + labels: + vendor: crunchydata rules: - apiGroups: - '' @@ -17,6 +19,8 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: pgo-cluster-role + labels: + vendor: crunchydata roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole diff --git a/installers/ansible/roles/pgo-operator/templates/cluster-rbac.yaml.j2 b/installers/ansible/roles/pgo-operator/templates/cluster-rbac.yaml.j2 index 4212d9107b..03783810da 100644 --- a/installers/ansible/roles/pgo-operator/templates/cluster-rbac.yaml.j2 +++ b/installers/ansible/roles/pgo-operator/templates/cluster-rbac.yaml.j2 @@ -3,6 +3,8 @@ kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: pgo-cluster-role + labels: + vendor: crunchydata rules: - apiGroups: - '' @@ -111,6 +113,8 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: pgo-cluster-role + labels: + vendor: crunchydata roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole diff --git a/installers/ansible/roles/pgo-operator/templates/local-namespace-rbac.yaml.j2 b/installers/ansible/roles/pgo-operator/templates/local-namespace-rbac.yaml.j2 index 4a878395ae..6eb3fe6144 100644 --- a/installers/ansible/roles/pgo-operator/templates/local-namespace-rbac.yaml.j2 +++ b/installers/ansible/roles/pgo-operator/templates/local-namespace-rbac.yaml.j2 @@ -3,6 +3,8 @@ kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: name: pgo-local-ns + labels: + vendor: crunchydata rules: - apiGroups: - '' @@ -28,6 +30,8 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: pgo-local-ns + labels: + vendor: crunchydata roleRef: apiGroup: rbac.authorization.k8s.io kind: Role @@ -41,6 +45,8 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: pgo-target-role-binding + labels: + vendor: crunchydata roleRef: apiGroup: rbac.authorization.k8s.io kind: Role diff --git a/installers/ansible/roles/pgo-operator/templates/pgo-role-rbac.yaml.j2 b/installers/ansible/roles/pgo-operator/templates/pgo-role-rbac.yaml.j2 index 76af49dbcd..62ecb0a1b3 100644 --- a/installers/ansible/roles/pgo-operator/templates/pgo-role-rbac.yaml.j2 +++ b/installers/ansible/roles/pgo-operator/templates/pgo-role-rbac.yaml.j2 @@ -4,6 +4,8 @@ apiVersion: rbac.authorization.k8s.io/v1 metadata: name: pgo-role namespace: {{ pgo_operator_namespace }} + labels: + vendor: crunchydata rules: - apiGroups: - '' @@ -28,6 +30,8 @@ kind: RoleBinding metadata: name: pgo-role namespace: {{ pgo_operator_namespace }} + labels: + vendor: crunchydata roleRef: apiGroup: rbac.authorization.k8s.io kind: Role diff --git a/installers/ansible/roles/pgo-operator/templates/pgo-service-account.yaml.j2 b/installers/ansible/roles/pgo-operator/templates/pgo-service-account.yaml.j2 index b8a8de6a95..3baaa4a9f8 100644 --- a/installers/ansible/roles/pgo-operator/templates/pgo-service-account.yaml.j2 +++ b/installers/ansible/roles/pgo-operator/templates/pgo-service-account.yaml.j2 @@ -4,6 +4,8 @@ kind: ServiceAccount metadata: name: postgres-operator namespace: {{ pgo_operator_namespace }} + labels: + vendor: crunchydata imagePullSecrets: {% if ccp_image_pull_secret %} - name: {{ ccp_image_pull_secret }} diff --git a/installers/ansible/roles/pgo-operator/templates/service.json.j2 b/installers/ansible/roles/pgo-operator/templates/service.json.j2 index 766a060a72..b50985fa92 100644 --- a/installers/ansible/roles/pgo-operator/templates/service.json.j2 +++ b/installers/ansible/roles/pgo-operator/templates/service.json.j2 @@ -4,7 +4,8 @@ "metadata": { "name": "postgres-operator", "labels": { - "name": "postgres-operator" + "name": "postgres-operator", + "vendor": "crunchydata" } }, "spec": { diff --git a/installers/kubectl/postgres-operator-ocp311.yml b/installers/kubectl/postgres-operator-ocp311.yml index d19d9ffec1..302381d85b 100644 --- a/installers/kubectl/postgres-operator-ocp311.yml +++ b/installers/kubectl/postgres-operator-ocp311.yml @@ -3,12 +3,16 @@ kind: ServiceAccount metadata: name: pgo-deployer-sa namespace: pgo + labels: + vendor: crunchydata --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: pgo-deployer-crb namespace: pgo + labels: + vendor: crunchydata roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole @@ -23,6 +27,8 @@ kind: ConfigMap metadata: name: pgo-deployer-cm namespace: pgo + labels: + vendor: crunchydata data: values.yaml: |- # ===================== @@ -151,11 +157,15 @@ kind: Job metadata: name: pgo-deploy namespace: pgo + labels: + vendor: crunchydata spec: backoffLimit: 0 template: metadata: name: pgo-deploy + labels: + vendor: crunchydata spec: serviceAccountName: pgo-deployer-sa restartPolicy: Never diff --git a/installers/kubectl/postgres-operator.yml b/installers/kubectl/postgres-operator.yml index 2dabf8e782..3cfd9d2e53 100644 --- a/installers/kubectl/postgres-operator.yml +++ b/installers/kubectl/postgres-operator.yml @@ -3,11 +3,15 @@ kind: ServiceAccount metadata: name: pgo-deployer-sa namespace: pgo + labels: + vendor: crunchydata --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: pgo-deployer-cr + labels: + vendor: crunchydata rules: - apiGroups: - '' @@ -118,6 +122,8 @@ kind: ConfigMap metadata: name: pgo-deployer-cm namespace: pgo + labels: + vendor: crunchydata data: values.yaml: |- # ===================== @@ -245,6 +251,8 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: pgo-deployer-crb + labels: + vendor: crunchydata roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole @@ -259,11 +267,15 @@ kind: Job metadata: name: pgo-deploy namespace: pgo + labels: + vendor: crunchydata spec: backoffLimit: 0 template: metadata: name: pgo-deploy + labels: + vendor: crunchydata spec: serviceAccountName: pgo-deployer-sa restartPolicy: Never diff --git a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml index 0b9873951c..050b54a578 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml @@ -5,6 +5,7 @@ metadata: namespace: pgo labels: app.kubernetes.io/name: postgres-operator-monitoring + vendor: crunchydata --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding @@ -13,6 +14,7 @@ metadata: namespace: pgo labels: app.kubernetes.io/name: postgres-operator-monitoring + vendor: crunchydata roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole @@ -29,6 +31,7 @@ metadata: namespace: pgo labels: app.kubernetes.io/name: postgres-operator-monitoring + vendor: crunchydata data: values.yaml: |- # ===================== @@ -84,6 +87,7 @@ metadata: namespace: pgo labels: app.kubernetes.io/name: postgres-operator-monitoring + vendor: crunchydata spec: backoffLimit: 0 template: @@ -91,6 +95,7 @@ spec: name: pgo-metrics-deploy labels: app.kubernetes.io/name: postgres-operator-monitoring + vendor: crunchydata spec: serviceAccountName: pgo-metrics-deployer-sa restartPolicy: Never diff --git a/installers/metrics/kubectl/postgres-operator-metrics.yml b/installers/metrics/kubectl/postgres-operator-metrics.yml index 9b646e0033..7c9e8d1ca4 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics.yml @@ -5,6 +5,7 @@ metadata: namespace: pgo labels: app.kubernetes.io/name: postgres-operator-monitoring + vendor: crunchydata --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 @@ -12,6 +13,7 @@ metadata: name: pgo-metrics-deployer-cr labels: app.kubernetes.io/name: postgres-operator-monitoring + vendor: crunchydata rules: - apiGroups: - '' @@ -83,6 +85,7 @@ metadata: namespace: pgo labels: app.kubernetes.io/name: postgres-operator-monitoring + vendor: crunchydata data: values.yaml: |- # ===================== @@ -137,6 +140,7 @@ metadata: name: pgo-metrics-deployer-crb labels: app.kubernetes.io/name: postgres-operator-monitoring + vendor: crunchydata roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole @@ -153,6 +157,7 @@ metadata: namespace: pgo labels: app.kubernetes.io/name: postgres-operator-monitoring + vendor: crunchydata spec: backoffLimit: 0 template: @@ -160,6 +165,7 @@ spec: name: pgo-metrics-deploy labels: app.kubernetes.io/name: postgres-operator-monitoring + vendor: crunchydata spec: serviceAccountName: pgo-metrics-deployer-sa restartPolicy: Never diff --git a/installers/olm/description.openshift.md b/installers/olm/description.openshift.md index 81c2238916..f515d8817a 100644 --- a/installers/olm/description.openshift.md +++ b/installers/olm/description.openshift.md @@ -71,6 +71,7 @@ If you plan to use AWS S3 to store backups, you can configure your environment t oc -n "$PGO_OPERATOR_NAMESPACE" create secret generic pgo-backrest-repo-config \ --from-literal=aws-s3-key="" \ --from-literal=aws-s3-key-secret="" +oc -n "$PGO_OPERATOR_NAMESPACE" label secret pgo-backrest-repo-config vendor=crunchydata ``` ### Certificates (optional) diff --git a/installers/olm/description.upstream.md b/installers/olm/description.upstream.md index 4c6d2484b5..a5871f6ea9 100644 --- a/installers/olm/description.upstream.md +++ b/installers/olm/description.upstream.md @@ -65,6 +65,7 @@ If you plan to use AWS S3 to store backups, you can configure your environment t kubectl -n "$PGO_OPERATOR_NAMESPACE" create secret generic pgo-backrest-repo-config \ --from-literal=aws-s3-key="" \ --from-literal=aws-s3-key-secret="" +kubectl -n "$PGO_OPERATOR_NAMESPACE" label secret pgo-backrest-repo-config vendor=crunchydata ``` ### Certificates (optional) diff --git a/installers/olm/postgresoperator.crd.yaml b/installers/olm/postgresoperator.crd.yaml index 9d9da35997..c8516550e1 100644 --- a/installers/olm/postgresoperator.crd.yaml +++ b/installers/olm/postgresoperator.crd.yaml @@ -3,6 +3,8 @@ apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: pgclusters.crunchydata.com + labels: + vendor: crunchydata spec: group: crunchydata.com names: @@ -37,6 +39,8 @@ apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: pgpolicies.crunchydata.com + labels: + vendor: crunchydata spec: group: crunchydata.com names: @@ -58,6 +62,8 @@ apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: pgreplicas.crunchydata.com + labels: + vendor: crunchydata spec: group: crunchydata.com names: @@ -79,6 +85,8 @@ apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: pgtasks.crunchydata.com + labels: + vendor: crunchydata spec: group: crunchydata.com names: diff --git a/internal/config/pgoconfig.go b/internal/config/pgoconfig.go index 281fdf6600..234bf93ef3 100644 --- a/internal/config/pgoconfig.go +++ b/internal/config/pgoconfig.go @@ -740,6 +740,9 @@ func initialize(clientset kubernetes.Interface, namespace string) (*v1.ConfigMap cm := &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: CustomConfigMapName, + Labels: map[string]string{ + LABEL_VENDOR: LABEL_CRUNCHY, + }, }, Data: map[string]string{}, } diff --git a/internal/operator/common.go b/internal/operator/common.go index e78f447556..f0536d79fa 100644 --- a/internal/operator/common.go +++ b/internal/operator/common.go @@ -473,6 +473,9 @@ func initializeOperatorBackrestSecret(clientset kubernetes.Interface, namespace secret = &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: config.SecretOperatorBackrestRepoConfig, + Labels: map[string]string{ + config.LABEL_VENDOR: config.LABEL_CRUNCHY, + }, }, Data: map[string][]byte{}, } From 9ff2443ac847f72837a93d29c7092df41c6f92f2 Mon Sep 17 00:00:00 2001 From: Heath Lord Date: Wed, 4 Aug 2021 12:09:06 -0400 Subject: [PATCH 238/276] Update pgo-apiserver for newer buildah versions Must add ARG for PGVERSION after the FROM or it doesn't get set with newer buildah versions. --- build/pgo-apiserver/Dockerfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build/pgo-apiserver/Dockerfile b/build/pgo-apiserver/Dockerfile index a2ec3c3b3a..505ea1b4a9 100644 --- a/build/pgo-apiserver/Dockerfile +++ b/build/pgo-apiserver/Dockerfile @@ -5,6 +5,10 @@ ARG PGVERSION ARG BACKREST_VERSION FROM ${PREFIX}/pgo-base:${BASEOS}-${BASEVER} +ARG BASEOS +ARG PACKAGER +ARG PGVERSION + LABEL name="pgo-apiserver" \ summary="Crunchy PostgreSQL Operator - Apiserver" \ description="Crunchy PostgreSQL Operator - Apiserver" From e4e536f9169681573391254e8d45513dc38ec80e Mon Sep 17 00:00:00 2001 From: Andrew L'Ecuyer Date: Tue, 24 Aug 2021 16:28:43 +0000 Subject: [PATCH 239/276] Image Updates Image updates for PGO scripts, installers, examples and documentation. Issue: [ch12324] --- Makefile | 4 +-- README.md | 2 +- bin/push-ccp-to-gcr.sh | 2 +- conf/postgres-operator/pgo.yaml | 4 +-- docs/config.toml | 14 +++++------ docs/content/Configuration/compatibility.md | 6 +++++ docs/content/releases/4.6.4.md | 25 +++++++++++++++++++ examples/create-by-resource/fromcrd.json | 6 ++--- examples/envs.sh | 2 +- examples/helm/README.md | 4 +-- examples/helm/postgres/Chart.yaml | 2 +- .../helm/postgres/templates/pgcluster.yaml | 2 +- examples/helm/postgres/values.yaml | 2 +- examples/kustomize/createcluster/README.md | 16 ++++++------ .../createcluster/base/pgcluster.yaml | 6 ++--- .../overlay/staging/hippo-rpl1-pgreplica.yaml | 2 +- installers/ansible/README.md | 2 +- installers/ansible/values.yaml | 6 ++--- installers/gcp-marketplace/Makefile | 2 +- installers/gcp-marketplace/README.md | 2 +- installers/gcp-marketplace/values.yaml | 6 ++--- installers/helm/Chart.yaml | 2 +- installers/helm/values.yaml | 6 ++--- installers/kubectl/client-setup.sh | 2 +- .../kubectl/postgres-operator-ocp311.yml | 8 +++--- installers/kubectl/postgres-operator.yml | 8 +++--- installers/metrics/ansible/README.md | 2 +- installers/metrics/helm/Chart.yaml | 2 +- installers/metrics/helm/helm_template.yaml | 2 +- installers/metrics/helm/values.yaml | 2 +- .../postgres-operator-metrics-ocp311.yml | 2 +- .../kubectl/postgres-operator-metrics.yml | 2 +- installers/olm/Makefile | 4 +-- installers/olm/description.openshift.md | 2 +- installers/olm/description.upstream.md | 2 +- internal/operator/cluster/upgrade.go | 2 +- pkg/apis/crunchydata.com/v1/doc.go | 8 +++--- pkg/apiservermsgs/common.go | 2 +- redhat/atomic/help.1 | 2 +- redhat/atomic/help.md | 2 +- 40 files changed, 105 insertions(+), 74 deletions(-) create mode 100644 docs/content/releases/4.6.4.md diff --git a/Makefile b/Makefile index 4148f80512..ca9b99a303 100644 --- a/Makefile +++ b/Makefile @@ -5,9 +5,9 @@ PGOROOT ?= $(CURDIR) PGO_BASEOS ?= centos8 PGO_IMAGE_PREFIX ?= crunchydata PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) -PGO_VERSION ?= 4.6.3 +PGO_VERSION ?= 4.6.4 PGO_PG_VERSION ?= 13 -PGO_PG_FULLVERSION ?= 13.3 +PGO_PG_FULLVERSION ?= 13.4 PGO_BACKREST_VERSION ?= 2.31 PACKAGER ?= yum diff --git a/README.md b/README.md index a9a679bad9..3522e48002 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ to start as quickly as: ```shell kubectl create namespace pgo -kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.3/installers/kubectl/postgres-operator.yml +kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.4/installers/kubectl/postgres-operator.yml ``` Otherwise, we highly recommend following the instructions from our [Quickstart](https://access.crunchydata.com/documentation/postgres-operator/latest/quickstart/). diff --git a/bin/push-ccp-to-gcr.sh b/bin/push-ccp-to-gcr.sh index 20fb0f9c5a..c9df87cb16 100755 --- a/bin/push-ccp-to-gcr.sh +++ b/bin/push-ccp-to-gcr.sh @@ -16,7 +16,7 @@ GCR_IMAGE_PREFIX=gcr.io/crunchy-dev-test CCP_IMAGE_PREFIX=crunchydata -CCP_IMAGE_TAG=centos8-13.3-4.6.3 +CCP_IMAGE_TAG=centos8-13.4-4.6.4 IMAGES=( crunchy-prometheus diff --git a/conf/postgres-operator/pgo.yaml b/conf/postgres-operator/pgo.yaml index b43ccf0661..2c1e65570d 100644 --- a/conf/postgres-operator/pgo.yaml +++ b/conf/postgres-operator/pgo.yaml @@ -2,7 +2,7 @@ Cluster: CCPImagePrefix: registry.developers.crunchydata.com/crunchydata Metrics: false Badger: false - CCPImageTag: centos8-13.3-4.6.3 + CCPImageTag: centos8-13.4-4.6.4 Port: 5432 PGBadgerPort: 10000 ExporterPort: 9187 @@ -80,4 +80,4 @@ Storage: Pgo: Audit: false PGOImagePrefix: registry.developers.crunchydata.com/crunchydata - PGOImageTag: centos8-4.6.3 + PGOImageTag: centos8-4.6.4 diff --git a/docs/config.toml b/docs/config.toml index 0e5122d6dc..8c7b462d41 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -25,13 +25,13 @@ disableNavChevron = false # set true to hide next/prev chevron, default is false highlightClientSide = false # set true to use highlight.pack.js instead of the default hugo chroma highlighter menushortcutsnewtab = true # set true to open shortcuts links to a new tab/window enableGitInfo = true -operatorVersion = "4.6.3" -postgresVersion = "13.3" -postgresVersion13 = "13.3" -postgresVersion12 = "12.6" -postgresVersion11 = "11.11" -postgresVersion10 = "10.16" -postgresVersion96 = "9.6.21" +operatorVersion = "4.6.4" +postgresVersion = "13.4" +postgresVersion13 = "13.4" +postgresVersion12 = "12.8" +postgresVersion11 = "11.13" +postgresVersion10 = "10.18" +postgresVersion96 = "9.6.23" postgresVersion95 = "9.5.25" postgisVersion = "3.0" centosBase = "centos8" diff --git a/docs/content/Configuration/compatibility.md b/docs/content/Configuration/compatibility.md index de40a08b70..130c80eac7 100644 --- a/docs/content/Configuration/compatibility.md +++ b/docs/content/Configuration/compatibility.md @@ -12,6 +12,12 @@ version dependencies between the two projects. Below are the operator releases a | Operator Release | Container Release | Postgres | PgBackrest Version |:----------|:-------------|:------------|:-------------- +| 4.6.4 | 4.6.4 | 13.4 | 2.31 | +|||12.8|2.31| +|||11.13|2.31| +|||10.18|2.31| +|||9.6.23|2.31| +|||| | 4.6.3 | 4.6.3 | 13.3 | 2.31 | |||12.7|2.31| |||11.12|2.31| diff --git a/docs/content/releases/4.6.4.md b/docs/content/releases/4.6.4.md new file mode 100644 index 0000000000..6122b4f876 --- /dev/null +++ b/docs/content/releases/4.6.4.md @@ -0,0 +1,25 @@ +--- +title: "4.6.4" +date: +draft: false +weight: 56 +--- + +Crunchy Data announces the release of PGO, the Postgres Operator 4.6.4. + +The PostgreSQL Operator is released in conjunction with the [Crunchy Container Suite](https://github.com/CrunchyData/crunchy-containers/). + +PostgreSQL Operator 4.6.4 release includes the following software versions upgrades: + +- [PostgreSQL](https://www.postgresql.org) is updated to 13.4, 12.8, 11.13, 10.18, and 9.6.23. +- PL/Tcl is now included in the PostGIS (`crunchy-postgres-gis-ha`) container. + +## Changes + +- On using the built-in upgrade tool, the `pg-pod-anti-affinity` is now removed from the `userlabels` section of a `pgclusters.crunchydata.com` custom resource. +- Ensure `vendor` label is propagated to all PGO managed objects. Reported by (@mdraijer). + +## Fixes + +- Allow backup configuration to be changed when a cluster is recreated. For example, allow backup configuration to change from posix to s3 within a new cluster. +- Ensure a SQL policy that contains writes can be applied to a Postgres cluster after a failover. diff --git a/examples/create-by-resource/fromcrd.json b/examples/create-by-resource/fromcrd.json index 5b07ed7f9a..e5b8dddc83 100644 --- a/examples/create-by-resource/fromcrd.json +++ b/examples/create-by-resource/fromcrd.json @@ -10,7 +10,7 @@ "deployment-name": "fromcrd", "name": "fromcrd", "pg-cluster": "fromcrd", - "pgo-version": "4.6.3", + "pgo-version": "4.6.4", "pgouser": "pgoadmin" }, "name": "fromcrd", @@ -45,7 +45,7 @@ "supplementalgroups": "" }, "ccpimage": "crunchy-postgres-ha", - "ccpimagetag": "centos8-13.3-4.6.3", + "ccpimagetag": "centos8-13.4-4.6.4", "clustername": "fromcrd", "database": "userdb", "exporterport": "9187", @@ -60,7 +60,7 @@ "port": "5432", "user": "testuser", "userlabels": { - "pgo-version": "4.6.3" + "pgo-version": "4.6.4" } } } diff --git a/examples/envs.sh b/examples/envs.sh index fe9e178d2c..cb41389d99 100644 --- a/examples/envs.sh +++ b/examples/envs.sh @@ -20,7 +20,7 @@ export PGO_CONF_DIR=$PGOROOT/installers/ansible/roles/pgo-operator/files # the version of the Operator you run is set by these vars export PGO_IMAGE_PREFIX=registry.developers.crunchydata.com/crunchydata export PGO_BASEOS=centos8 -export PGO_VERSION=4.6.3 +export PGO_VERSION=4.6.4 export PGO_IMAGE_TAG=$PGO_BASEOS-$PGO_VERSION # for setting the pgo apiserver port, disabling TLS or not verifying TLS diff --git a/examples/helm/README.md b/examples/helm/README.md index cec91edba0..81d19f464c 100644 --- a/examples/helm/README.md +++ b/examples/helm/README.md @@ -64,7 +64,7 @@ The following values can also be set: - `ha`: Whether or not to deploy a high availability PostgreSQL cluster. Can be either `true` or `false`, defaults to `false`. - `imagePrefix`: The prefix of the container images to use for this PostgreSQL cluster. Default to `registry.developers.crunchydata.com/crunchydata`. - `image`: The name of the container image to use for the PostgreSQL cluster. Defaults to `crunchy-postgres-ha`. -- `imageTag`: The container image tag to use. Defaults to `centos8-13.3-4.6.3`. +- `imageTag`: The container image tag to use. Defaults to `centos8-13.4-4.6.4`. - `memory`: The memory limit for the PostgreSQL cluster. Follows standard Kubernetes formatting. - `monitoring`: Whether or not to enable monitoring / metrics collection for this PostgreSQL instance. Can either be `true` or `false`, defaults to `false`. @@ -103,7 +103,7 @@ PGPASSWORD="W4tch0ut4hippo$" psql -h localhost -U hippo hippo ## Notes -Prior to PostgreSQL Operator 4.6.3, you will have to manually clean up some of the artifacts when running `helm uninstall`. +Prior to PostgreSQL Operator 4.6.4, you will have to manually clean up some of the artifacts when running `helm uninstall`. ## Additional Resources diff --git a/examples/helm/postgres/Chart.yaml b/examples/helm/postgres/Chart.yaml index 7b02e573e3..729217d1dc 100644 --- a/examples/helm/postgres/Chart.yaml +++ b/examples/helm/postgres/Chart.yaml @@ -20,4 +20,4 @@ version: 0.2.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. -appVersion: 4.6.3 +appVersion: 4.6.4 diff --git a/examples/helm/postgres/templates/pgcluster.yaml b/examples/helm/postgres/templates/pgcluster.yaml index 7346e29795..f4aec2b390 100644 --- a/examples/helm/postgres/templates/pgcluster.yaml +++ b/examples/helm/postgres/templates/pgcluster.yaml @@ -28,7 +28,7 @@ spec: storagetype: dynamic ccpimage: {{ .Values.image | default "crunchy-postgres-ha" | quote }} ccpimageprefix: {{ .Values.imagePrefix | default "registry.developers.crunchydata.com/crunchydata" | quote }} - ccpimagetag: {{ .Values.imageTag | default "centos8-13.3-4.6.3" | quote }} + ccpimagetag: {{ .Values.imageTag | default "centos8-13.4-4.6.4" | quote }} clustername: {{ .Values.name | quote }} database: {{ .Values.name | quote }} {{- if .Values.monitoring }} diff --git a/examples/helm/postgres/values.yaml b/examples/helm/postgres/values.yaml index 6e80fe5ae9..774d900fed 100644 --- a/examples/helm/postgres/values.yaml +++ b/examples/helm/postgres/values.yaml @@ -10,5 +10,5 @@ password: W4tch0ut4hippo$ # ha: true # imagePrefix: registry.developers.crunchydata.com/crunchydata # image: crunchy-postgres-ha -# imageTag: centos8-13.3-4.6.3 +# imageTag: centos8-13.4-4.6.4 # memory: 1Gi diff --git a/examples/kustomize/createcluster/README.md b/examples/kustomize/createcluster/README.md index b399a06bfc..d2763ca6e3 100644 --- a/examples/kustomize/createcluster/README.md +++ b/examples/kustomize/createcluster/README.md @@ -44,13 +44,13 @@ pgo show cluster hippo -n pgo ``` You will see something like this if successful: ``` -cluster : hippo (crunchy-postgres-ha:centos8-13.3-4.6.3) +cluster : hippo (crunchy-postgres-ha:centos8-13.4-4.6.4) pod : hippo-8fb6bd96-j87wq (Running) on gke-xxxx-default-pool-38e946bd-257w (1/1) (primary) pvc: hippo (1Gi) deployment : hippo deployment : hippo-backrest-shared-repo service : hippo - ClusterIP (10.0.56.86) - Ports (2022/TCP, 5432/TCP) - labels : pgo-version=4.6.3 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.4 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata ``` Feel free to run other pgo cli commands on the hippo cluster @@ -79,7 +79,7 @@ pgo show cluster dev-hippo -n pgo ``` You will see something like this if successful: ``` -cluster : dev-hippo (crunchy-postgres-ha:centos8-13.3-4.6.3) +cluster : dev-hippo (crunchy-postgres-ha:centos8-13.4-4.6.4) pod : dev-hippo-588d4cb746-bwrxb (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: dev-hippo (1Gi) deployment : dev-hippo @@ -87,7 +87,7 @@ cluster : dev-hippo (crunchy-postgres-ha:centos8-13.3-4.6.3) deployment : dev-hippo-pgbouncer service : dev-hippo - ClusterIP (10.0.62.87) - Ports (2022/TCP, 5432/TCP) service : dev-hippo-pgbouncer - ClusterIP (10.0.48.120) - Ports (5432/TCP) - labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.6.3 pgouser=admin + labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.6.4 pgouser=admin ``` #### staging The staging overlay will deploy a crunchy postgreSQL cluster with 2 replica's with annotations added @@ -113,7 +113,7 @@ pgo show cluster staging-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : staging-hippo (crunchy-postgres-ha:centos8-13.3-4.6.3) +cluster : staging-hippo (crunchy-postgres-ha:centos8-13.4-4.6.4) pod : staging-hippo-85cf6dcb65-9h748 (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: staging-hippo (1Gi) pod : staging-hippo-lnxw-cf47d8c8b-6r4wn (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (replica) @@ -128,7 +128,7 @@ cluster : staging-hippo (crunchy-postgres-ha:centos8-13.3-4.6.3) service : staging-hippo-replica - ClusterIP (10.0.56.57) - Ports (2022/TCP, 5432/TCP) pgreplica : staging-hippo-lnxw pgreplica : staging-hippo-rpl1 - labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.6.3 pgouser=admin vendor=crunchydata + labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.6.4 pgouser=admin vendor=crunchydata ``` #### production @@ -154,7 +154,7 @@ pgo show cluster prod-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : prod-hippo (crunchy-postgres-ha:centos8-13.3-4.6.3) +cluster : prod-hippo (crunchy-postgres-ha:centos8-13.4-4.6.4) pod : prod-hippo-5d6dd46497-rr67c (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (primary) pvc: prod-hippo (1Gi) pod : prod-hippo-flty-84d97c8769-2pzbh (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (replica) @@ -165,7 +165,7 @@ cluster : prod-hippo (crunchy-postgres-ha:centos8-13.3-4.6.3) service : prod-hippo - ClusterIP (10.0.56.18) - Ports (2022/TCP, 5432/TCP) service : prod-hippo-replica - ClusterIP (10.0.56.101) - Ports (2022/TCP, 5432/TCP) pgreplica : prod-hippo-flty - labels : pgo-version=4.6.3 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.4 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata ``` ### Delete the clusters To delete the clusters run the following pgo cli commands diff --git a/examples/kustomize/createcluster/base/pgcluster.yaml b/examples/kustomize/createcluster/base/pgcluster.yaml index 386e60aeaa..a57eafd84a 100644 --- a/examples/kustomize/createcluster/base/pgcluster.yaml +++ b/examples/kustomize/createcluster/base/pgcluster.yaml @@ -10,7 +10,7 @@ metadata: deployment-name: hippo name: hippo pg-cluster: hippo - pgo-version: 4.6.3 + pgo-version: 4.6.4 pgouser: admin name: hippo namespace: pgo @@ -46,7 +46,7 @@ spec: postgres: {} ccpimage: crunchy-postgres-ha ccpimageprefix: registry.developers.crunchydata.com/crunchydata - ccpimagetag: centos8-13.3-4.6.3 + ccpimagetag: centos8-13.4-4.6.4 clustername: hippo customconfig: "" database: hippo @@ -69,4 +69,4 @@ spec: port: "5432" user: hippo userlabels: - pgo-version: 4.6.3 + pgo-version: 4.6.4 diff --git a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml index aa69384539..5f43510535 100644 --- a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml +++ b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml @@ -20,4 +20,4 @@ spec: storagetype: dynamic supplementalgroups: "" userlabels: - pgo-version: 4.6.3 + pgo-version: 4.6.4 diff --git a/installers/ansible/README.md b/installers/ansible/README.md index eddc7044aa..42e1608778 100644 --- a/installers/ansible/README.md +++ b/installers/ansible/README.md @@ -4,7 +4,7 @@ PGO: The Postgres Operator from Crunchy Data

-Latest Release: 4.6.3 +Latest Release: 4.6.4 ## General diff --git a/installers/ansible/values.yaml b/installers/ansible/values.yaml index 509eaf533a..06676b3e2f 100644 --- a/installers/ansible/values.yaml +++ b/installers/ansible/values.yaml @@ -17,7 +17,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.3-4.6.3" +ccp_image_tag: "centos8-13.4-4.6.4" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -49,14 +49,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.3" +pgo_client_version: "4.6.4" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos8-4.6.3" +pgo_image_tag: "centos8-4.6.4" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/gcp-marketplace/Makefile b/installers/gcp-marketplace/Makefile index c86ea556b9..a3024e6284 100644 --- a/installers/gcp-marketplace/Makefile +++ b/installers/gcp-marketplace/Makefile @@ -6,7 +6,7 @@ MARKETPLACE_TOOLS ?= gcr.io/cloud-marketplace-tools/k8s/dev:$(MARKETPLACE_VERSIO MARKETPLACE_VERSION ?= 0.9.4 KUBECONFIG ?= $(HOME)/.kube/config PARAMETERS ?= {} -PGO_VERSION ?= 4.6.3 +PGO_VERSION ?= 4.6.4 IMAGE_BUILD_ARGS = --build-arg MARKETPLACE_VERSION='$(MARKETPLACE_VERSION)' \ --build-arg PGO_VERSION='$(PGO_VERSION)' diff --git a/installers/gcp-marketplace/README.md b/installers/gcp-marketplace/README.md index 37435c359b..20c1daade8 100644 --- a/installers/gcp-marketplace/README.md +++ b/installers/gcp-marketplace/README.md @@ -59,7 +59,7 @@ Google Cloud Marketplace. ```shell IMAGE_REPOSITORY=gcr.io/crunchydata-public/postgres-operator - export PGO_VERSION=4.6.3 + export PGO_VERSION=4.6.4 export INSTALLER_IMAGE=${IMAGE_REPOSITORY}/deployer:${PGO_VERSION} export OPERATOR_IMAGE=${IMAGE_REPOSITORY}:${PGO_VERSION} export OPERATOR_IMAGE_API=${IMAGE_REPOSITORY}/pgo-apiserver:${PGO_VERSION} diff --git a/installers/gcp-marketplace/values.yaml b/installers/gcp-marketplace/values.yaml index 24c99783a6..65dcce437f 100644 --- a/installers/gcp-marketplace/values.yaml +++ b/installers/gcp-marketplace/values.yaml @@ -10,7 +10,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.3-4.6.3" +ccp_image_tag: "centos8-13.4-4.6.4" create_rbac: "true" db_name: "" db_password_age_days: "0" @@ -32,9 +32,9 @@ pgo_admin_role_name: "pgoadmin" pgo_admin_username: "admin" pgo_client_container_install: "false" pgo_client_install: 'false' -pgo_client_version: "4.6.3" +pgo_client_version: "4.6.4" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.3" +pgo_image_tag: "centos8-4.6.4" pgo_installation_name: '${OPERATOR_NAME}' pgo_operator_namespace: '${OPERATOR_NAMESPACE}' scheduler_timeout: "3600" diff --git a/installers/helm/Chart.yaml b/installers/helm/Chart.yaml index 24610041b0..8767ec7b1f 100644 --- a/installers/helm/Chart.yaml +++ b/installers/helm/Chart.yaml @@ -3,7 +3,7 @@ name: postgres-operator description: 'PGO: The Postgres Operator from Crunchy Data Helm Chart for Kubernetes' type: application version: 0.2.0 -appVersion: 4.6.3 +appVersion: 4.6.4 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/docs/static/logos/pgo.svg keywords: diff --git a/installers/helm/values.yaml b/installers/helm/values.yaml index c1fbbe5332..7ad9d755ea 100644 --- a/installers/helm/values.yaml +++ b/installers/helm/values.yaml @@ -37,7 +37,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.3-4.6.3" +ccp_image_tag: "centos8-13.4-4.6.4" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -69,14 +69,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.3" +pgo_client_version: "4.6.4" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos8-4.6.3" +pgo_image_tag: "centos8-4.6.4" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/kubectl/client-setup.sh b/installers/kubectl/client-setup.sh index aac97906c7..aebae48408 100755 --- a/installers/kubectl/client-setup.sh +++ b/installers/kubectl/client-setup.sh @@ -14,7 +14,7 @@ # This script should be run after the operator has been deployed PGO_OPERATOR_NAMESPACE="${PGO_OPERATOR_NAMESPACE:-pgo}" PGO_USER_ADMIN="${PGO_USER_ADMIN:-pgouser-admin}" -PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.3}" +PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.4}" PGO_CLIENT_URL="https://github.com/CrunchyData/postgres-operator/releases/download/${PGO_CLIENT_VERSION}" PGO_CMD="${PGO_CMD-kubectl}" diff --git a/installers/kubectl/postgres-operator-ocp311.yml b/installers/kubectl/postgres-operator-ocp311.yml index 302381d85b..76a8ee611f 100644 --- a/installers/kubectl/postgres-operator-ocp311.yml +++ b/installers/kubectl/postgres-operator-ocp311.yml @@ -50,7 +50,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos8-13.3-4.6.3" + ccp_image_tag: "centos8-13.4-4.6.4" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -83,14 +83,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.3" + pgo_client_version: "4.6.4" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos8-4.6.3" + pgo_image_tag: "centos8-4.6.4" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -171,7 +171,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.3 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.4 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/kubectl/postgres-operator.yml b/installers/kubectl/postgres-operator.yml index 3cfd9d2e53..1202e77e80 100644 --- a/installers/kubectl/postgres-operator.yml +++ b/installers/kubectl/postgres-operator.yml @@ -145,7 +145,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos8-13.3-4.6.3" + ccp_image_tag: "centos8-13.4-4.6.4" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -178,14 +178,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.3" + pgo_client_version: "4.6.4" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos8-4.6.3" + pgo_image_tag: "centos8-4.6.4" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -281,7 +281,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.3 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.4 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/ansible/README.md b/installers/metrics/ansible/README.md index 2dd03f551c..9b9252e8dd 100644 --- a/installers/metrics/ansible/README.md +++ b/installers/metrics/ansible/README.md @@ -4,7 +4,7 @@ Crunchy Data

-Latest Release: 4.6.3 +Latest Release: 4.6.4 ## General diff --git a/installers/metrics/helm/Chart.yaml b/installers/metrics/helm/Chart.yaml index a04a002b04..5dabc57259 100644 --- a/installers/metrics/helm/Chart.yaml +++ b/installers/metrics/helm/Chart.yaml @@ -3,6 +3,6 @@ name: postgres-operator-monitoring description: Install for Crunchy PostgreSQL Operator Monitoring type: application version: 0.2.0 -appVersion: 4.6.3 +appVersion: 4.6.4 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/docs/static/logos/pgo.svg diff --git a/installers/metrics/helm/helm_template.yaml b/installers/metrics/helm/helm_template.yaml index cf78dfc1e3..2c3a7646e0 100644 --- a/installers/metrics/helm/helm_template.yaml +++ b/installers/metrics/helm/helm_template.yaml @@ -20,5 +20,5 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.3" +pgo_image_tag: "centos8-4.6.4" diff --git a/installers/metrics/helm/values.yaml b/installers/metrics/helm/values.yaml index a39df1cf6c..18dcb0a1f2 100644 --- a/installers/metrics/helm/values.yaml +++ b/installers/metrics/helm/values.yaml @@ -20,7 +20,7 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.3" +pgo_image_tag: "centos8-4.6.4" # ===================== # Configuration Options diff --git a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml index 050b54a578..94be90301f 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml @@ -101,7 +101,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.3 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.4 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/kubectl/postgres-operator-metrics.yml b/installers/metrics/kubectl/postgres-operator-metrics.yml index 7c9e8d1ca4..ceb8e7df7f 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics.yml @@ -171,7 +171,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.3 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.4 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/olm/Makefile b/installers/olm/Makefile index b725a2b669..eab8fae439 100644 --- a/installers/olm/Makefile +++ b/installers/olm/Makefile @@ -2,7 +2,7 @@ .SUFFIXES: CCP_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -CCP_PG_FULLVERSION ?= 13.3 +CCP_PG_FULLVERSION ?= 13.4 CCP_POSTGIS_VERSION ?= 3.0 CONTAINER ?= docker KUBECONFIG ?= $(HOME)/.kube/config @@ -11,7 +11,7 @@ OLM_TOOLS ?= registry.localhost:5000/postgres-operator-olm-tools:$(OLM_SDK_VERSI OLM_VERSION ?= 0.15.1 PGO_BASEOS ?= centos8 PGO_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -PGO_VERSION ?= 4.6.3 +PGO_VERSION ?= 4.6.4 PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) CCP_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(PGO_VERSION) CCP_POSTGIS_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(CCP_POSTGIS_VERSION)-$(PGO_VERSION) diff --git a/installers/olm/description.openshift.md b/installers/olm/description.openshift.md index f515d8817a..41766538ca 100644 --- a/installers/olm/description.openshift.md +++ b/installers/olm/description.openshift.md @@ -188,7 +188,7 @@ spec: annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: ${cluster_image_prefix} - ccpimagetag: centos8-13.3-${PGO_VERSION} + ccpimagetag: centos8-13.4-${PGO_VERSION} clustername: ${pgo_cluster_name} database: ${pgo_cluster_name} exporterport: "9187" diff --git a/installers/olm/description.upstream.md b/installers/olm/description.upstream.md index a5871f6ea9..02495d0cfd 100644 --- a/installers/olm/description.upstream.md +++ b/installers/olm/description.upstream.md @@ -168,7 +168,7 @@ spec: annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: ${cluster_image_prefix} - ccpimagetag: centos8-13.3-${PGO_VERSION} + ccpimagetag: centos8-13.4-${PGO_VERSION} clustername: ${pgo_cluster_name} database: ${pgo_cluster_name} exporterport: "9187" diff --git a/internal/operator/cluster/upgrade.go b/internal/operator/cluster/upgrade.go index f12c1ed7d8..c96dfdcc32 100644 --- a/internal/operator/cluster/upgrade.go +++ b/internal/operator/cluster/upgrade.go @@ -958,7 +958,7 @@ func updatePGBackRestSSHDConfig(clientset kubernetes.Interface, repoSecret *v1.S ctx := context.TODO() updatedRepoSecret := repoSecret.DeepCopy() - // For versions prior to v4.6.3, the UsePAM setting might be set to 'yes' as previously + // For versions prior to v4.6.4, the UsePAM setting might be set to 'yes' as previously // required to workaround a known Docker issue. Since this issue has since been resolved, // we now want to ensure this setting is set to 'no'. if !usePAMRegex.MatchString(string(updatedRepoSecret.Data["sshd_config"])) { diff --git a/pkg/apis/crunchydata.com/v1/doc.go b/pkg/apis/crunchydata.com/v1/doc.go index 88b11f6742..dec2194a18 100644 --- a/pkg/apis/crunchydata.com/v1/doc.go +++ b/pkg/apis/crunchydata.com/v1/doc.go @@ -53,7 +53,7 @@ cluster. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.3", + '{"ClientVersion":"4.6.4", "Namespace":"pgouser1", "Name":"mycluster", $PGO_APISERVER_URL/clusters @@ -72,7 +72,7 @@ show all of the clusters that are in the given namespace. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.3", + '{"ClientVersion":"4.6.4", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/showclusters @@ -82,7 +82,7 @@ $PGO_APISERVER_URL/showclusters curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.3", + '{"ClientVersion":"4.6.4", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/clustersdelete @@ -90,7 +90,7 @@ $PGO_APISERVER_URL/clustersdelete Schemes: http, https BasePath: / - Version: 4.6.3 + Version: 4.6.4 License: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0 Contact: Crunchy Data https://www.crunchydata.com/ diff --git a/pkg/apiservermsgs/common.go b/pkg/apiservermsgs/common.go index a548c42272..6bb03b09da 100644 --- a/pkg/apiservermsgs/common.go +++ b/pkg/apiservermsgs/common.go @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -const PGO_VERSION = "4.6.3" +const PGO_VERSION = "4.6.4" // Ok status const Ok = "ok" diff --git a/redhat/atomic/help.1 b/redhat/atomic/help.1 index 49b47b99fd..c48336b3fe 100644 --- a/redhat/atomic/help.1 +++ b/redhat/atomic/help.1 @@ -56,4 +56,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa \fB\fCRelease=\fR .PP -The specific release number of the container. For example, Release="4.6.3" +The specific release number of the container. For example, Release="4.6.4" diff --git a/redhat/atomic/help.md b/redhat/atomic/help.md index c829251582..0b8d5f70ff 100644 --- a/redhat/atomic/help.md +++ b/redhat/atomic/help.md @@ -45,4 +45,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa `Release=` -The specific release number of the container. For example, Release="4.6.3" +The specific release number of the container. For example, Release="4.6.4" From aebcae202a82b1f4f3614ea2af1b26e0443f0773 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 30 Aug 2021 10:35:38 -0400 Subject: [PATCH 240/276] Updates to v4.6.4 release notes --- docs/content/releases/4.6.4.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/content/releases/4.6.4.md b/docs/content/releases/4.6.4.md index 6122b4f876..74bcf1cefa 100644 --- a/docs/content/releases/4.6.4.md +++ b/docs/content/releases/4.6.4.md @@ -12,7 +12,8 @@ The PostgreSQL Operator is released in conjunction with the [Crunchy Container S PostgreSQL Operator 4.6.4 release includes the following software versions upgrades: - [PostgreSQL](https://www.postgresql.org) is updated to 13.4, 12.8, 11.13, 10.18, and 9.6.23. -- PL/Tcl is now included in the PostGIS (`crunchy-postgres-gis-ha`) container. +- [pgaudit_analyze](https://github.com/pgaudit/pgaudit_analyze) is now at 1.0.8. +- [set_user](https://github.com/pgaudit/set_user) is now at version 2.0.1. ## Changes From ebdfe63da0b5a835832f23162121098a9314e2d5 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Thu, 14 Oct 2021 12:12:22 -0400 Subject: [PATCH 241/276] Update OpenShift detection logic This looks for the presence of SecurityContextConstraints as the delimiter for if this is running in an OpenShift cluster. With more Kubernetes APIs having an OpenShift suffix that can run in other environments, this gives a stronger check of actually running in an OpenShift cluster. Issue: [sc-12837] Issue: #2778 --- internal/config/pgoconfig.go | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/internal/config/pgoconfig.go b/internal/config/pgoconfig.go index 234bf93ef3..5150168ba4 100644 --- a/internal/config/pgoconfig.go +++ b/internal/config/pgoconfig.go @@ -39,9 +39,10 @@ import ( ) const ( - CustomConfigMapName = "pgo-config" - defaultConfigPath = "/default-pgo-config/" - openShiftAPIGroupSuffix = ".openshift.io" + CustomConfigMapName = "pgo-config" + defaultConfigPath = "/default-pgo-config/" + openShiftSCCGroup = "security.openshift.io" + openShiftSCCKind = "SecurityContextConstraints" ) var PgoDefaultServiceAccountTemplate *template.Template @@ -875,18 +876,24 @@ func (c *PgoConfig) DisableFSGroup() bool { // isOpenShift returns true if we've detected that we're in an OpenShift cluster func isOpenShift(clientset kubernetes.Interface) bool { - groups, _, err := clientset.Discovery().ServerGroupsAndResources() + _, resourceLists, err := clientset.Discovery().ServerGroupsAndResources() if err != nil { log.Errorf("could not get server api groups: %s", err.Error()) return false } - // ff we detect that any API group name ends with "openshift.io", we'll return - // that this is an OpenShift environment - for _, g := range groups { - if strings.HasSuffix(g.Name, openShiftAPIGroupSuffix) { - return true + // If we detect that the "SecurityContextConstraints" Kind is present in the + // "security.openshift.io" Group, we'll return that this is an OpenShift + // environment + for _, rl := range resourceLists { + if strings.HasPrefix(rl.GroupVersion, openShiftSCCGroup+"/") { + for _, r := range rl.APIResources { + if r.Kind == openShiftSCCKind { + log.Info("detected OpenShift environment") + return true + } + } } } From 1e85587e80427728d378dd1e04335f124fd221db Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Fri, 12 Nov 2021 11:18:18 -0500 Subject: [PATCH 242/276] Ensure pgBouncer resource limits can be set on create This only affects the `pgo create pgbouncer` command; the resource limits were not being set in the custom resource from the API. However, this functionally works correctly if set directly on the custom resource. Issue: [sc-13146] --- cmd/pgo/cmd/create.go | 2 +- internal/apiserver/pgbouncerservice/pgbouncerimpl.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/pgo/cmd/create.go b/cmd/pgo/cmd/create.go index aabfa84e74..217ba5f2eb 100644 --- a/cmd/pgo/cmd/create.go +++ b/cmd/pgo/cmd/create.go @@ -530,7 +530,7 @@ func init() { // pgo create pgbouncer createPgbouncerCmd.Flags().StringVar(&PgBouncerCPURequest, "cpu", "", "Set the number of millicores to request for CPU "+ "for pgBouncer. Defaults to being unset.") - createPgbouncerCmd.Flags().StringVar(&PgBouncerCPULimit, "cpu-limit", "", "Set the number of millicores to request for CPU "+ + createPgbouncerCmd.Flags().StringVar(&PgBouncerCPULimit, "cpu-limit", "", "Set the number of millicores to limit for CPU "+ "for pgBouncer.") createPgbouncerCmd.Flags().StringVar(&PgBouncerMemoryRequest, "memory", "", "Set the amount of memory to request for "+ "pgBouncer. Defaults to server value (24Mi).") diff --git a/internal/apiserver/pgbouncerservice/pgbouncerimpl.go b/internal/apiserver/pgbouncerservice/pgbouncerimpl.go index 92030852e5..872b922ca6 100644 --- a/internal/apiserver/pgbouncerservice/pgbouncerimpl.go +++ b/internal/apiserver/pgbouncerservice/pgbouncerimpl.go @@ -149,6 +149,7 @@ func CreatePgbouncer(request *msgs.CreatePgbouncerRequest, ns, pgouser string) m } cluster.Spec.PgBouncer.Resources = resources + cluster.Spec.PgBouncer.Limits = limits cluster.Spec.PgBouncer.TLSSecret = request.TLSSecret // update the cluster CRD with these udpates. If there is an error From 5d4a21d6f0d118dff3ba0a80cfe0d2cfe6d55bf9 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 16 Nov 2021 09:18:53 -0500 Subject: [PATCH 243/276] Ensure "pgo delete backup" works for non-POSIX storage This modifies the "pgo delete backup" command to work with non-POSIX storage, i.e. S3, or any combination of the like. Issue: [sc-13149] Issue: #2850 --- .../apiserver/backrestservice/backrestimpl.go | 49 +++++++++++++++++-- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/internal/apiserver/backrestservice/backrestimpl.go b/internal/apiserver/backrestservice/backrestimpl.go index 67c276fe14..812b14f95c 100644 --- a/internal/apiserver/backrestservice/backrestimpl.go +++ b/internal/apiserver/backrestservice/backrestimpl.go @@ -266,16 +266,55 @@ func DeleteBackup(request msgs.DeleteBackrestBackupRequest) msgs.DeleteBackrestB return response } + // determine if TLS verification is enabled or not + verifyTLS, _ := strconv.ParseBool(operator.GetS3VerifyTLSSetting(cluster)) + // set up the command cmd := pgBackRestExpireCommand cmd = append(cmd, request.Target) - // and execute. if there is an error, return it, otherwise we are done - if _, stderr, err := kubeapi.ExecToPodThroughAPI(apiserver.RESTConfig, - apiserver.Clientset, cmd, containername, podName, cluster.Spec.Namespace, nil); err != nil { - log.Error(stderr) + // first, if storage types is empty, assume it's the posix storage type + storageTypes := cluster.Spec.BackrestStorageTypes + if len(storageTypes) == 0 { + storageTypes = append(storageTypes, crv1.BackrestStorageTypePosix) + } + + // otherwise, iterate through the different repositories types that are + // available. if it's a non-local repository, we need to set an explicit + // "--repo-type" + ok := false + + for _, storageType := range storageTypes { + c := cmd + + switch storageType { + default: // do nothing + case crv1.BackrestStorageTypeS3: + c = append(c, repoTypeFlagS3...) + + if !verifyTLS { + c = append(c, noRepoS3VerifyTLS) + } + } + + // so...we don't necessarily care about the error here, because we're + // looking for which of the repos contains the target backup. We'll log the + // error, and return it if we don't have success + if _, stderr, err := kubeapi.ExecToPodThroughAPI(apiserver.RESTConfig, + apiserver.Clientset, c, containername, podName, cluster.Namespace, nil); err != nil { + log.Infof("repo type %s does not contain backup %s or other error.", storageType, request.Target) + log.Info(stderr) + } else { + ok = true + } + } + + // if we don't ever delete the backup, provide a message as to why + if !ok { + msg := fmt.Sprintf("could not find backup %s in any repo or check logs for other errors.", request.Target) + log.Errorf(msg) response.Code = msgs.Error - response.Msg = stderr + response.Msg = msg } return response From 8e374b774532a2376488550eb1dca4fd0b93f741 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Fri, 19 Nov 2021 14:29:09 -0500 Subject: [PATCH 244/276] Update AWS CA bundle for interfacing with S3 This bundle was changed per URL below: https://aws.amazon.com/blogs/security/how-to-prepare-for-aws-move-to-its-own-certificate-authority/ Issue: [sc-13185] --- .../files/pgo-backrest-repo/aws-s3-ca.crt | 141 +++++++++++++++--- 1 file changed, 122 insertions(+), 19 deletions(-) diff --git a/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/aws-s3-ca.crt b/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/aws-s3-ca.crt index 519028c63b..31ebd8a6e5 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/aws-s3-ca.crt +++ b/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/aws-s3-ca.crt @@ -1,21 +1,124 @@ -----BEGIN CERTIFICATE----- -MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ -RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD -VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX -DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y -ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy -VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr -mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr -IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK -mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu -XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy -dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye -jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 -BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 -DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 -9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx -jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 -Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz -ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS -R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj +ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM +9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw +IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 +VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L +93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm +jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA +A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI +U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs +N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv +o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU +5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy +rqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK +gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ +W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg +1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K +8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r +2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me +z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR +8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj +mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz +7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6 ++XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI +0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm +UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2 +LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS +k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl +7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm +btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl +urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+ +fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63 +n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE +76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H +9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT +4PsJYGw= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl +ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr +ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr +BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM +YyRIHN8wfdVoOw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi +9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk +M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB +MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw +CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW +1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs +ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD +VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy +ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy +dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p +OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2 +8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K +Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe +hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk +6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q +AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI +bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB +ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z +qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn +0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN +sSi6 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl +MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp +U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw +NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE +ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp +ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3 +DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf +8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN ++lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0 +X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa +K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA +1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G +A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR +zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0 +YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD +bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w +DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3 +L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D +eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp +VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY +WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= -----END CERTIFICATE----- From 2b7085937a07b6d471b71695f93f02ee6c9eb95c Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Fri, 19 Nov 2021 15:21:32 -0500 Subject: [PATCH 245/276] Allow for seamless upgrade to new AWS S3 CA bundle This updates the autodetection logic to add the new AWS S3 CA bundle to the general PGO Secret, which is then applied to clusters on upgrade. The logic is such that it will only overwrite the default template if it is unmodified, i.e. it is using the CA bundle that is provided. --- internal/operator/cluster/upgrade.go | 19 +++++++++++++++++++ internal/operator/common.go | 17 +++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/internal/operator/cluster/upgrade.go b/internal/operator/cluster/upgrade.go index c96dfdcc32..e2d5dcb3b5 100644 --- a/internal/operator/cluster/upgrade.go +++ b/internal/operator/cluster/upgrade.go @@ -17,9 +17,11 @@ package cluster import ( "context" + "crypto/sha256" "errors" "fmt" "io/ioutil" + "path" "regexp" "strconv" "strings" @@ -57,6 +59,10 @@ const ( // v4.5.2 and v4.4.3) var usePAMRegex = regexp.MustCompile(`(?im)^UsePAM\s*yes`) +// legacyS3CASHA256Digest informs us if we should override the S3 CA with the +// new bundle +const legacyS3CASHA256Digest = "d1c290ea1e4544dec1934931fbfa1fb2060eb3a0f2239ba191f444ecbce35cbb" + // AddUpgrade implements the upgrade workflow in accordance with the received pgtask // the general process is outlined below: // 1) get the existing pgcluster CRD instance that matches the name provided in the pgtask @@ -465,6 +471,19 @@ func recreateBackrestRepoSecret(clientset kubernetes.Interface, clustername, nam if err == nil { if b, ok := secret.Data["aws-s3-ca.crt"]; ok { config.BackrestS3CA = b + + // if this matches the old AWS S3 CA bundle, update to the new one. + if fmt.Sprintf("%x", sha256.Sum256(config.BackrestS3CA)) == legacyS3CASHA256Digest { + file := path.Join("/default-pgo-backrest-repo/aws-s3-ca.crt") + + // if we can't read the contents of the file for whatever reason, warn, + // otherwise, update the entry in the Secret + if contents, err := ioutil.ReadFile(file); err != nil { + log.Warn(err) + } else { + config.BackrestS3CA = contents + } + } } if b, ok := secret.Data["aws-s3-key"]; ok { config.BackrestS3Key = string(b) diff --git a/internal/operator/common.go b/internal/operator/common.go index f0536d79fa..be71596b63 100644 --- a/internal/operator/common.go +++ b/internal/operator/common.go @@ -18,6 +18,7 @@ package operator import ( "bytes" "context" + "crypto/sha256" "encoding/json" "fmt" "io/ioutil" @@ -47,6 +48,9 @@ const ( defaultBackrestRepoConfigPath = "/default-pgo-backrest-repo/" // defaultRegistry is the default registry to pull the container images from defaultRegistry = "registry.developers.crunchydata.com/crunchydata" + // legacyS3CASHA256Digest informs us if we should override the S3 CA with the + // new bundle + legacyS3CASHA256Digest = "d1c290ea1e4544dec1934931fbfa1fb2060eb3a0f2239ba191f444ecbce35cbb" ) var ( @@ -484,9 +488,18 @@ func initializeOperatorBackrestSecret(clientset kubernetes.Interface, namespace // set any missing defaults for _, filename := range defaultBackrestRepoConfigKeys { - // skip if there is already content + // skip if there is already content, unless this is aws-s3-ca.crt due to + // the change in the CA bundle if len(secret.Data[filename]) != 0 { - continue + if filename != "aws-s3-ca.crt" { + continue + } + + // in the case of aws-s3-ca.crt, check that this is the default + // certificate. if it is, override it + if fmt.Sprintf("%x", sha256.Sum256(secret.Data[filename])) != legacyS3CASHA256Digest { + continue + } } file := path.Join(defaultBackrestRepoConfigPath, filename) From 3c60d90149aabbe28f3916d9f6c6a94a6d2b381d Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Mon, 22 Nov 2021 12:06:46 -0500 Subject: [PATCH 246/276] Version bump v4.6.5 --- Makefile | 4 +-- README.md | 2 +- bin/push-ccp-to-gcr.sh | 2 +- conf/postgres-operator/pgo.yaml | 4 +-- docs/config.toml | 15 +++++------ docs/content/Configuration/compatibility.md | 6 +++++ .../advanced/crunchy-postgres-exporter.md | 2 +- docs/content/releases/4.6.5.md | 27 +++++++++++++++++++ examples/create-by-resource/fromcrd.json | 6 ++--- examples/envs.sh | 2 +- examples/helm/README.md | 4 +-- examples/helm/postgres/Chart.yaml | 2 +- .../helm/postgres/templates/pgcluster.yaml | 2 +- examples/helm/postgres/values.yaml | 2 +- examples/kustomize/createcluster/README.md | 16 +++++------ .../createcluster/base/pgcluster.yaml | 6 ++--- .../overlay/staging/hippo-rpl1-pgreplica.yaml | 2 +- installers/ansible/README.md | 2 +- installers/ansible/values.yaml | 6 ++--- installers/gcp-marketplace/Makefile | 2 +- installers/gcp-marketplace/README.md | 2 +- installers/gcp-marketplace/values.yaml | 6 ++--- installers/helm/Chart.yaml | 2 +- installers/helm/values.yaml | 6 ++--- installers/kubectl/client-setup.sh | 2 +- .../kubectl/postgres-operator-ocp311.yml | 8 +++--- installers/kubectl/postgres-operator.yml | 8 +++--- installers/metrics/ansible/README.md | 2 +- installers/metrics/helm/Chart.yaml | 2 +- installers/metrics/helm/helm_template.yaml | 2 +- installers/metrics/helm/values.yaml | 2 +- .../postgres-operator-metrics-ocp311.yml | 2 +- .../kubectl/postgres-operator-metrics.yml | 2 +- installers/olm/Makefile | 4 +-- installers/olm/description.openshift.md | 2 +- installers/olm/description.upstream.md | 2 +- internal/operator/cluster/upgrade.go | 2 +- pkg/apis/crunchydata.com/v1/doc.go | 8 +++--- pkg/apiservermsgs/common.go | 2 +- redhat/atomic/help.1 | 2 +- redhat/atomic/help.md | 2 +- 41 files changed, 108 insertions(+), 76 deletions(-) create mode 100644 docs/content/releases/4.6.5.md diff --git a/Makefile b/Makefile index ca9b99a303..26270df410 100644 --- a/Makefile +++ b/Makefile @@ -5,9 +5,9 @@ PGOROOT ?= $(CURDIR) PGO_BASEOS ?= centos8 PGO_IMAGE_PREFIX ?= crunchydata PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) -PGO_VERSION ?= 4.6.4 +PGO_VERSION ?= 4.6.5 PGO_PG_VERSION ?= 13 -PGO_PG_FULLVERSION ?= 13.4 +PGO_PG_FULLVERSION ?= 13.5 PGO_BACKREST_VERSION ?= 2.31 PACKAGER ?= yum diff --git a/README.md b/README.md index 3522e48002..395852894c 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ to start as quickly as: ```shell kubectl create namespace pgo -kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.4/installers/kubectl/postgres-operator.yml +kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.5/installers/kubectl/postgres-operator.yml ``` Otherwise, we highly recommend following the instructions from our [Quickstart](https://access.crunchydata.com/documentation/postgres-operator/latest/quickstart/). diff --git a/bin/push-ccp-to-gcr.sh b/bin/push-ccp-to-gcr.sh index c9df87cb16..4d0460e51b 100755 --- a/bin/push-ccp-to-gcr.sh +++ b/bin/push-ccp-to-gcr.sh @@ -16,7 +16,7 @@ GCR_IMAGE_PREFIX=gcr.io/crunchy-dev-test CCP_IMAGE_PREFIX=crunchydata -CCP_IMAGE_TAG=centos8-13.4-4.6.4 +CCP_IMAGE_TAG=centos8-13.5-4.6.5 IMAGES=( crunchy-prometheus diff --git a/conf/postgres-operator/pgo.yaml b/conf/postgres-operator/pgo.yaml index 2c1e65570d..49ca4c412c 100644 --- a/conf/postgres-operator/pgo.yaml +++ b/conf/postgres-operator/pgo.yaml @@ -2,7 +2,7 @@ Cluster: CCPImagePrefix: registry.developers.crunchydata.com/crunchydata Metrics: false Badger: false - CCPImageTag: centos8-13.4-4.6.4 + CCPImageTag: centos8-13.5-4.6.5 Port: 5432 PGBadgerPort: 10000 ExporterPort: 9187 @@ -80,4 +80,4 @@ Storage: Pgo: Audit: false PGOImagePrefix: registry.developers.crunchydata.com/crunchydata - PGOImageTag: centos8-4.6.4 + PGOImageTag: centos8-4.6.5 diff --git a/docs/config.toml b/docs/config.toml index 8c7b462d41..ecd0b59da7 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -25,14 +25,13 @@ disableNavChevron = false # set true to hide next/prev chevron, default is false highlightClientSide = false # set true to use highlight.pack.js instead of the default hugo chroma highlighter menushortcutsnewtab = true # set true to open shortcuts links to a new tab/window enableGitInfo = true -operatorVersion = "4.6.4" -postgresVersion = "13.4" -postgresVersion13 = "13.4" -postgresVersion12 = "12.8" -postgresVersion11 = "11.13" -postgresVersion10 = "10.18" -postgresVersion96 = "9.6.23" -postgresVersion95 = "9.5.25" +operatorVersion = "4.6.5" +postgresVersion = "13.5" +postgresVersion13 = "13.5" +postgresVersion12 = "12.9" +postgresVersion11 = "11.14" +postgresVersion10 = "10.19" +postgresVersion96 = "9.6.24" postgisVersion = "3.0" centosBase = "centos8" diff --git a/docs/content/Configuration/compatibility.md b/docs/content/Configuration/compatibility.md index 130c80eac7..aa90276db1 100644 --- a/docs/content/Configuration/compatibility.md +++ b/docs/content/Configuration/compatibility.md @@ -12,6 +12,12 @@ version dependencies between the two projects. Below are the operator releases a | Operator Release | Container Release | Postgres | PgBackrest Version |:----------|:-------------|:------------|:-------------- +| 4.6.5 | 4.6.5 | 13.5 | 2.31 | +|||12.9|2.31| +|||11.14|2.31| +|||10.19|2.31| +|||9.6.24|2.31| +|||| | 4.6.4 | 4.6.4 | 13.4 | 2.31 | |||12.8|2.31| |||11.13|2.31| diff --git a/docs/content/advanced/crunchy-postgres-exporter.md b/docs/content/advanced/crunchy-postgres-exporter.md index 16a58e5672..aa0f96eadf 100644 --- a/docs/content/advanced/crunchy-postgres-exporter.md +++ b/docs/content/advanced/crunchy-postgres-exporter.md @@ -23,7 +23,7 @@ can be specified for the API to collect. For an example of a queries.yml file, s The crunchy-postgres-exporter Docker image contains the following packages (versions vary depending on PostgreSQL version): -* PostgreSQL ({{< param postgresVersion13 >}}, {{< param postgresVersion12 >}}, {{< param postgresVersion11 >}}, {{< param postgresVersion10 >}}, {{< param postgresVersion96 >}} and {{< param postgresVersion95 >}}) +* PostgreSQL ({{< param postgresVersion13 >}}, {{< param postgresVersion12 >}}, {{< param postgresVersion11 >}}, {{< param postgresVersion10 >}}, and {{< param postgresVersion96 >}}. * CentOS 8 - publicly available * UBI 7, UBI 8 - customers only * [PostgreSQL Exporter](https://github.com/wrouesnel/postgres_exporter) diff --git a/docs/content/releases/4.6.5.md b/docs/content/releases/4.6.5.md new file mode 100644 index 0000000000..b381d936a8 --- /dev/null +++ b/docs/content/releases/4.6.5.md @@ -0,0 +1,27 @@ +--- +title: "4.6.5" +date: +draft: false +weight: 55 +--- + +Crunchy Data announces the release of PGO, the Postgres Operator 4.6.5. + +The PostgreSQL Operator is released in conjunction with the [Crunchy Container Suite](https://github.com/CrunchyData/crunchy-containers/). + +PostgreSQL Operator 4.6.5 release includes the following software versions upgrades: + +- [PostgreSQL](https://www.postgresql.org) versions 13.5, 12.9, 11.14, 10.19 and 9.6.24 are now available. +- The [pgnodemx](https://github.com/CrunchyData/pgnodemx) extension is now at version 1.0.6. +- [pgBouncer](https://www.pgbouncer.org/) is now at version 1.16.1 +- The [pgAudit](https://github.com/pgaudit/pgaudit) extension is now at version 1.6.1 + +## Changes + +- Update automatic OpenShift detection logic to look specifically for the presence of the SecurityContextConstraint API. Reported by (@aurelien43). + +## Fixes + +- Ensure the `pgo create pgbouncer` command can set CPU and memory limits via `--cpu-limit` and `--memory-limit` respectively. +- Ensure `pgo delete backup` works with backups stored in S3 or GCS. Reported by Munjal Patel (@munjalpatel). +- Update the `aws-s3-ca.crt` value to use the newer CAs provided by AWS. If a PostgreSQL cluster is using the old default CA, PGO will update the general one kept in the `pgo-backrest-repo-config` Secret and `pgo upgrade` will update it for a specific cluster.S diff --git a/examples/create-by-resource/fromcrd.json b/examples/create-by-resource/fromcrd.json index e5b8dddc83..ce66563361 100644 --- a/examples/create-by-resource/fromcrd.json +++ b/examples/create-by-resource/fromcrd.json @@ -10,7 +10,7 @@ "deployment-name": "fromcrd", "name": "fromcrd", "pg-cluster": "fromcrd", - "pgo-version": "4.6.4", + "pgo-version": "4.6.5", "pgouser": "pgoadmin" }, "name": "fromcrd", @@ -45,7 +45,7 @@ "supplementalgroups": "" }, "ccpimage": "crunchy-postgres-ha", - "ccpimagetag": "centos8-13.4-4.6.4", + "ccpimagetag": "centos8-13.5-4.6.5", "clustername": "fromcrd", "database": "userdb", "exporterport": "9187", @@ -60,7 +60,7 @@ "port": "5432", "user": "testuser", "userlabels": { - "pgo-version": "4.6.4" + "pgo-version": "4.6.5" } } } diff --git a/examples/envs.sh b/examples/envs.sh index cb41389d99..540623bd86 100644 --- a/examples/envs.sh +++ b/examples/envs.sh @@ -20,7 +20,7 @@ export PGO_CONF_DIR=$PGOROOT/installers/ansible/roles/pgo-operator/files # the version of the Operator you run is set by these vars export PGO_IMAGE_PREFIX=registry.developers.crunchydata.com/crunchydata export PGO_BASEOS=centos8 -export PGO_VERSION=4.6.4 +export PGO_VERSION=4.6.5 export PGO_IMAGE_TAG=$PGO_BASEOS-$PGO_VERSION # for setting the pgo apiserver port, disabling TLS or not verifying TLS diff --git a/examples/helm/README.md b/examples/helm/README.md index 81d19f464c..7a71acc392 100644 --- a/examples/helm/README.md +++ b/examples/helm/README.md @@ -64,7 +64,7 @@ The following values can also be set: - `ha`: Whether or not to deploy a high availability PostgreSQL cluster. Can be either `true` or `false`, defaults to `false`. - `imagePrefix`: The prefix of the container images to use for this PostgreSQL cluster. Default to `registry.developers.crunchydata.com/crunchydata`. - `image`: The name of the container image to use for the PostgreSQL cluster. Defaults to `crunchy-postgres-ha`. -- `imageTag`: The container image tag to use. Defaults to `centos8-13.4-4.6.4`. +- `imageTag`: The container image tag to use. Defaults to `centos8-13.5-4.6.5`. - `memory`: The memory limit for the PostgreSQL cluster. Follows standard Kubernetes formatting. - `monitoring`: Whether or not to enable monitoring / metrics collection for this PostgreSQL instance. Can either be `true` or `false`, defaults to `false`. @@ -103,7 +103,7 @@ PGPASSWORD="W4tch0ut4hippo$" psql -h localhost -U hippo hippo ## Notes -Prior to PostgreSQL Operator 4.6.4, you will have to manually clean up some of the artifacts when running `helm uninstall`. +Prior to PostgreSQL Operator 4.6.5, you will have to manually clean up some of the artifacts when running `helm uninstall`. ## Additional Resources diff --git a/examples/helm/postgres/Chart.yaml b/examples/helm/postgres/Chart.yaml index 729217d1dc..04afbcd55c 100644 --- a/examples/helm/postgres/Chart.yaml +++ b/examples/helm/postgres/Chart.yaml @@ -20,4 +20,4 @@ version: 0.2.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. -appVersion: 4.6.4 +appVersion: 4.6.5 diff --git a/examples/helm/postgres/templates/pgcluster.yaml b/examples/helm/postgres/templates/pgcluster.yaml index f4aec2b390..7920783a9f 100644 --- a/examples/helm/postgres/templates/pgcluster.yaml +++ b/examples/helm/postgres/templates/pgcluster.yaml @@ -28,7 +28,7 @@ spec: storagetype: dynamic ccpimage: {{ .Values.image | default "crunchy-postgres-ha" | quote }} ccpimageprefix: {{ .Values.imagePrefix | default "registry.developers.crunchydata.com/crunchydata" | quote }} - ccpimagetag: {{ .Values.imageTag | default "centos8-13.4-4.6.4" | quote }} + ccpimagetag: {{ .Values.imageTag | default "centos8-13.5-4.6.5" | quote }} clustername: {{ .Values.name | quote }} database: {{ .Values.name | quote }} {{- if .Values.monitoring }} diff --git a/examples/helm/postgres/values.yaml b/examples/helm/postgres/values.yaml index 774d900fed..8049810c68 100644 --- a/examples/helm/postgres/values.yaml +++ b/examples/helm/postgres/values.yaml @@ -10,5 +10,5 @@ password: W4tch0ut4hippo$ # ha: true # imagePrefix: registry.developers.crunchydata.com/crunchydata # image: crunchy-postgres-ha -# imageTag: centos8-13.4-4.6.4 +# imageTag: centos8-13.5-4.6.5 # memory: 1Gi diff --git a/examples/kustomize/createcluster/README.md b/examples/kustomize/createcluster/README.md index d2763ca6e3..1f1a478230 100644 --- a/examples/kustomize/createcluster/README.md +++ b/examples/kustomize/createcluster/README.md @@ -44,13 +44,13 @@ pgo show cluster hippo -n pgo ``` You will see something like this if successful: ``` -cluster : hippo (crunchy-postgres-ha:centos8-13.4-4.6.4) +cluster : hippo (crunchy-postgres-ha:centos8-13.5-4.6.5) pod : hippo-8fb6bd96-j87wq (Running) on gke-xxxx-default-pool-38e946bd-257w (1/1) (primary) pvc: hippo (1Gi) deployment : hippo deployment : hippo-backrest-shared-repo service : hippo - ClusterIP (10.0.56.86) - Ports (2022/TCP, 5432/TCP) - labels : pgo-version=4.6.4 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.5 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata ``` Feel free to run other pgo cli commands on the hippo cluster @@ -79,7 +79,7 @@ pgo show cluster dev-hippo -n pgo ``` You will see something like this if successful: ``` -cluster : dev-hippo (crunchy-postgres-ha:centos8-13.4-4.6.4) +cluster : dev-hippo (crunchy-postgres-ha:centos8-13.5-4.6.5) pod : dev-hippo-588d4cb746-bwrxb (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: dev-hippo (1Gi) deployment : dev-hippo @@ -87,7 +87,7 @@ cluster : dev-hippo (crunchy-postgres-ha:centos8-13.4-4.6.4) deployment : dev-hippo-pgbouncer service : dev-hippo - ClusterIP (10.0.62.87) - Ports (2022/TCP, 5432/TCP) service : dev-hippo-pgbouncer - ClusterIP (10.0.48.120) - Ports (5432/TCP) - labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.6.4 pgouser=admin + labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.6.5 pgouser=admin ``` #### staging The staging overlay will deploy a crunchy postgreSQL cluster with 2 replica's with annotations added @@ -113,7 +113,7 @@ pgo show cluster staging-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : staging-hippo (crunchy-postgres-ha:centos8-13.4-4.6.4) +cluster : staging-hippo (crunchy-postgres-ha:centos8-13.5-4.6.5) pod : staging-hippo-85cf6dcb65-9h748 (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: staging-hippo (1Gi) pod : staging-hippo-lnxw-cf47d8c8b-6r4wn (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (replica) @@ -128,7 +128,7 @@ cluster : staging-hippo (crunchy-postgres-ha:centos8-13.4-4.6.4) service : staging-hippo-replica - ClusterIP (10.0.56.57) - Ports (2022/TCP, 5432/TCP) pgreplica : staging-hippo-lnxw pgreplica : staging-hippo-rpl1 - labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.6.4 pgouser=admin vendor=crunchydata + labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.6.5 pgouser=admin vendor=crunchydata ``` #### production @@ -154,7 +154,7 @@ pgo show cluster prod-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : prod-hippo (crunchy-postgres-ha:centos8-13.4-4.6.4) +cluster : prod-hippo (crunchy-postgres-ha:centos8-13.5-4.6.5) pod : prod-hippo-5d6dd46497-rr67c (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (primary) pvc: prod-hippo (1Gi) pod : prod-hippo-flty-84d97c8769-2pzbh (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (replica) @@ -165,7 +165,7 @@ cluster : prod-hippo (crunchy-postgres-ha:centos8-13.4-4.6.4) service : prod-hippo - ClusterIP (10.0.56.18) - Ports (2022/TCP, 5432/TCP) service : prod-hippo-replica - ClusterIP (10.0.56.101) - Ports (2022/TCP, 5432/TCP) pgreplica : prod-hippo-flty - labels : pgo-version=4.6.4 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.5 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata ``` ### Delete the clusters To delete the clusters run the following pgo cli commands diff --git a/examples/kustomize/createcluster/base/pgcluster.yaml b/examples/kustomize/createcluster/base/pgcluster.yaml index a57eafd84a..9d9494d8a2 100644 --- a/examples/kustomize/createcluster/base/pgcluster.yaml +++ b/examples/kustomize/createcluster/base/pgcluster.yaml @@ -10,7 +10,7 @@ metadata: deployment-name: hippo name: hippo pg-cluster: hippo - pgo-version: 4.6.4 + pgo-version: 4.6.5 pgouser: admin name: hippo namespace: pgo @@ -46,7 +46,7 @@ spec: postgres: {} ccpimage: crunchy-postgres-ha ccpimageprefix: registry.developers.crunchydata.com/crunchydata - ccpimagetag: centos8-13.4-4.6.4 + ccpimagetag: centos8-13.5-4.6.5 clustername: hippo customconfig: "" database: hippo @@ -69,4 +69,4 @@ spec: port: "5432" user: hippo userlabels: - pgo-version: 4.6.4 + pgo-version: 4.6.5 diff --git a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml index 5f43510535..733491df5a 100644 --- a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml +++ b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml @@ -20,4 +20,4 @@ spec: storagetype: dynamic supplementalgroups: "" userlabels: - pgo-version: 4.6.4 + pgo-version: 4.6.5 diff --git a/installers/ansible/README.md b/installers/ansible/README.md index 42e1608778..8098a59a6a 100644 --- a/installers/ansible/README.md +++ b/installers/ansible/README.md @@ -4,7 +4,7 @@ PGO: The Postgres Operator from Crunchy Data

-Latest Release: 4.6.4 +Latest Release: 4.6.5 ## General diff --git a/installers/ansible/values.yaml b/installers/ansible/values.yaml index 06676b3e2f..f1cac20070 100644 --- a/installers/ansible/values.yaml +++ b/installers/ansible/values.yaml @@ -17,7 +17,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.4-4.6.4" +ccp_image_tag: "centos8-13.5-4.6.5" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -49,14 +49,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.4" +pgo_client_version: "4.6.5" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos8-4.6.4" +pgo_image_tag: "centos8-4.6.5" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/gcp-marketplace/Makefile b/installers/gcp-marketplace/Makefile index a3024e6284..850a7a5332 100644 --- a/installers/gcp-marketplace/Makefile +++ b/installers/gcp-marketplace/Makefile @@ -6,7 +6,7 @@ MARKETPLACE_TOOLS ?= gcr.io/cloud-marketplace-tools/k8s/dev:$(MARKETPLACE_VERSIO MARKETPLACE_VERSION ?= 0.9.4 KUBECONFIG ?= $(HOME)/.kube/config PARAMETERS ?= {} -PGO_VERSION ?= 4.6.4 +PGO_VERSION ?= 4.6.5 IMAGE_BUILD_ARGS = --build-arg MARKETPLACE_VERSION='$(MARKETPLACE_VERSION)' \ --build-arg PGO_VERSION='$(PGO_VERSION)' diff --git a/installers/gcp-marketplace/README.md b/installers/gcp-marketplace/README.md index 20c1daade8..b69405a5ca 100644 --- a/installers/gcp-marketplace/README.md +++ b/installers/gcp-marketplace/README.md @@ -59,7 +59,7 @@ Google Cloud Marketplace. ```shell IMAGE_REPOSITORY=gcr.io/crunchydata-public/postgres-operator - export PGO_VERSION=4.6.4 + export PGO_VERSION=4.6.5 export INSTALLER_IMAGE=${IMAGE_REPOSITORY}/deployer:${PGO_VERSION} export OPERATOR_IMAGE=${IMAGE_REPOSITORY}:${PGO_VERSION} export OPERATOR_IMAGE_API=${IMAGE_REPOSITORY}/pgo-apiserver:${PGO_VERSION} diff --git a/installers/gcp-marketplace/values.yaml b/installers/gcp-marketplace/values.yaml index 65dcce437f..b01da9bff1 100644 --- a/installers/gcp-marketplace/values.yaml +++ b/installers/gcp-marketplace/values.yaml @@ -10,7 +10,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.4-4.6.4" +ccp_image_tag: "centos8-13.5-4.6.5" create_rbac: "true" db_name: "" db_password_age_days: "0" @@ -32,9 +32,9 @@ pgo_admin_role_name: "pgoadmin" pgo_admin_username: "admin" pgo_client_container_install: "false" pgo_client_install: 'false' -pgo_client_version: "4.6.4" +pgo_client_version: "4.6.5" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.4" +pgo_image_tag: "centos8-4.6.5" pgo_installation_name: '${OPERATOR_NAME}' pgo_operator_namespace: '${OPERATOR_NAMESPACE}' scheduler_timeout: "3600" diff --git a/installers/helm/Chart.yaml b/installers/helm/Chart.yaml index 8767ec7b1f..8ca87255f7 100644 --- a/installers/helm/Chart.yaml +++ b/installers/helm/Chart.yaml @@ -3,7 +3,7 @@ name: postgres-operator description: 'PGO: The Postgres Operator from Crunchy Data Helm Chart for Kubernetes' type: application version: 0.2.0 -appVersion: 4.6.4 +appVersion: 4.6.5 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/docs/static/logos/pgo.svg keywords: diff --git a/installers/helm/values.yaml b/installers/helm/values.yaml index 7ad9d755ea..02c4ea37a9 100644 --- a/installers/helm/values.yaml +++ b/installers/helm/values.yaml @@ -37,7 +37,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.4-4.6.4" +ccp_image_tag: "centos8-13.5-4.6.5" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -69,14 +69,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.4" +pgo_client_version: "4.6.5" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos8-4.6.4" +pgo_image_tag: "centos8-4.6.5" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/kubectl/client-setup.sh b/installers/kubectl/client-setup.sh index aebae48408..1eaadc8c14 100755 --- a/installers/kubectl/client-setup.sh +++ b/installers/kubectl/client-setup.sh @@ -14,7 +14,7 @@ # This script should be run after the operator has been deployed PGO_OPERATOR_NAMESPACE="${PGO_OPERATOR_NAMESPACE:-pgo}" PGO_USER_ADMIN="${PGO_USER_ADMIN:-pgouser-admin}" -PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.4}" +PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.5}" PGO_CLIENT_URL="https://github.com/CrunchyData/postgres-operator/releases/download/${PGO_CLIENT_VERSION}" PGO_CMD="${PGO_CMD-kubectl}" diff --git a/installers/kubectl/postgres-operator-ocp311.yml b/installers/kubectl/postgres-operator-ocp311.yml index 76a8ee611f..c419044b85 100644 --- a/installers/kubectl/postgres-operator-ocp311.yml +++ b/installers/kubectl/postgres-operator-ocp311.yml @@ -50,7 +50,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos8-13.4-4.6.4" + ccp_image_tag: "centos8-13.5-4.6.5" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -83,14 +83,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.4" + pgo_client_version: "4.6.5" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos8-4.6.4" + pgo_image_tag: "centos8-4.6.5" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -171,7 +171,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.4 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.5 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/kubectl/postgres-operator.yml b/installers/kubectl/postgres-operator.yml index 1202e77e80..e8bd94a6c8 100644 --- a/installers/kubectl/postgres-operator.yml +++ b/installers/kubectl/postgres-operator.yml @@ -145,7 +145,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos8-13.4-4.6.4" + ccp_image_tag: "centos8-13.5-4.6.5" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -178,14 +178,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.4" + pgo_client_version: "4.6.5" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos8-4.6.4" + pgo_image_tag: "centos8-4.6.5" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -281,7 +281,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.4 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.5 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/ansible/README.md b/installers/metrics/ansible/README.md index 9b9252e8dd..4e3b1fc372 100644 --- a/installers/metrics/ansible/README.md +++ b/installers/metrics/ansible/README.md @@ -4,7 +4,7 @@ Crunchy Data

-Latest Release: 4.6.4 +Latest Release: 4.6.5 ## General diff --git a/installers/metrics/helm/Chart.yaml b/installers/metrics/helm/Chart.yaml index 5dabc57259..1e80dd5686 100644 --- a/installers/metrics/helm/Chart.yaml +++ b/installers/metrics/helm/Chart.yaml @@ -3,6 +3,6 @@ name: postgres-operator-monitoring description: Install for Crunchy PostgreSQL Operator Monitoring type: application version: 0.2.0 -appVersion: 4.6.4 +appVersion: 4.6.5 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/docs/static/logos/pgo.svg diff --git a/installers/metrics/helm/helm_template.yaml b/installers/metrics/helm/helm_template.yaml index 2c3a7646e0..2f52fed1cf 100644 --- a/installers/metrics/helm/helm_template.yaml +++ b/installers/metrics/helm/helm_template.yaml @@ -20,5 +20,5 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.4" +pgo_image_tag: "centos8-4.6.5" diff --git a/installers/metrics/helm/values.yaml b/installers/metrics/helm/values.yaml index 18dcb0a1f2..c3dd0cf78c 100644 --- a/installers/metrics/helm/values.yaml +++ b/installers/metrics/helm/values.yaml @@ -20,7 +20,7 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.4" +pgo_image_tag: "centos8-4.6.5" # ===================== # Configuration Options diff --git a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml index 94be90301f..0ab376a47b 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml @@ -101,7 +101,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.4 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.5 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/kubectl/postgres-operator-metrics.yml b/installers/metrics/kubectl/postgres-operator-metrics.yml index ceb8e7df7f..23f73114ce 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics.yml @@ -171,7 +171,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.4 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.5 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/olm/Makefile b/installers/olm/Makefile index eab8fae439..f9411dd395 100644 --- a/installers/olm/Makefile +++ b/installers/olm/Makefile @@ -2,7 +2,7 @@ .SUFFIXES: CCP_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -CCP_PG_FULLVERSION ?= 13.4 +CCP_PG_FULLVERSION ?= 13.5 CCP_POSTGIS_VERSION ?= 3.0 CONTAINER ?= docker KUBECONFIG ?= $(HOME)/.kube/config @@ -11,7 +11,7 @@ OLM_TOOLS ?= registry.localhost:5000/postgres-operator-olm-tools:$(OLM_SDK_VERSI OLM_VERSION ?= 0.15.1 PGO_BASEOS ?= centos8 PGO_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -PGO_VERSION ?= 4.6.4 +PGO_VERSION ?= 4.6.5 PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) CCP_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(PGO_VERSION) CCP_POSTGIS_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(CCP_POSTGIS_VERSION)-$(PGO_VERSION) diff --git a/installers/olm/description.openshift.md b/installers/olm/description.openshift.md index 41766538ca..7a33ebd0de 100644 --- a/installers/olm/description.openshift.md +++ b/installers/olm/description.openshift.md @@ -188,7 +188,7 @@ spec: annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: ${cluster_image_prefix} - ccpimagetag: centos8-13.4-${PGO_VERSION} + ccpimagetag: centos8-13.5-${PGO_VERSION} clustername: ${pgo_cluster_name} database: ${pgo_cluster_name} exporterport: "9187" diff --git a/installers/olm/description.upstream.md b/installers/olm/description.upstream.md index 02495d0cfd..a4da9dc171 100644 --- a/installers/olm/description.upstream.md +++ b/installers/olm/description.upstream.md @@ -168,7 +168,7 @@ spec: annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: ${cluster_image_prefix} - ccpimagetag: centos8-13.4-${PGO_VERSION} + ccpimagetag: centos8-13.5-${PGO_VERSION} clustername: ${pgo_cluster_name} database: ${pgo_cluster_name} exporterport: "9187" diff --git a/internal/operator/cluster/upgrade.go b/internal/operator/cluster/upgrade.go index e2d5dcb3b5..5fa3de7002 100644 --- a/internal/operator/cluster/upgrade.go +++ b/internal/operator/cluster/upgrade.go @@ -977,7 +977,7 @@ func updatePGBackRestSSHDConfig(clientset kubernetes.Interface, repoSecret *v1.S ctx := context.TODO() updatedRepoSecret := repoSecret.DeepCopy() - // For versions prior to v4.6.4, the UsePAM setting might be set to 'yes' as previously + // For versions prior to v4.6.2, the UsePAM setting might be set to 'yes' as previously // required to workaround a known Docker issue. Since this issue has since been resolved, // we now want to ensure this setting is set to 'no'. if !usePAMRegex.MatchString(string(updatedRepoSecret.Data["sshd_config"])) { diff --git a/pkg/apis/crunchydata.com/v1/doc.go b/pkg/apis/crunchydata.com/v1/doc.go index dec2194a18..46fdea5004 100644 --- a/pkg/apis/crunchydata.com/v1/doc.go +++ b/pkg/apis/crunchydata.com/v1/doc.go @@ -53,7 +53,7 @@ cluster. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.4", + '{"ClientVersion":"4.6.5", "Namespace":"pgouser1", "Name":"mycluster", $PGO_APISERVER_URL/clusters @@ -72,7 +72,7 @@ show all of the clusters that are in the given namespace. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.4", + '{"ClientVersion":"4.6.5", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/showclusters @@ -82,7 +82,7 @@ $PGO_APISERVER_URL/showclusters curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.4", + '{"ClientVersion":"4.6.5", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/clustersdelete @@ -90,7 +90,7 @@ $PGO_APISERVER_URL/clustersdelete Schemes: http, https BasePath: / - Version: 4.6.4 + Version: 4.6.5 License: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0 Contact: Crunchy Data https://www.crunchydata.com/ diff --git a/pkg/apiservermsgs/common.go b/pkg/apiservermsgs/common.go index 6bb03b09da..d24bf9de7f 100644 --- a/pkg/apiservermsgs/common.go +++ b/pkg/apiservermsgs/common.go @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -const PGO_VERSION = "4.6.4" +const PGO_VERSION = "4.6.5" // Ok status const Ok = "ok" diff --git a/redhat/atomic/help.1 b/redhat/atomic/help.1 index c48336b3fe..adb6354b97 100644 --- a/redhat/atomic/help.1 +++ b/redhat/atomic/help.1 @@ -56,4 +56,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa \fB\fCRelease=\fR .PP -The specific release number of the container. For example, Release="4.6.4" +The specific release number of the container. For example, Release="4.6.5" diff --git a/redhat/atomic/help.md b/redhat/atomic/help.md index 0b8d5f70ff..a9ef8e7541 100644 --- a/redhat/atomic/help.md +++ b/redhat/atomic/help.md @@ -45,4 +45,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa `Release=` -The specific release number of the container. For example, Release="4.6.4" +The specific release number of the container. For example, Release="4.6.5" From 9c91ffed781d7dc43d9bbb29fe8eec78f52bf091 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Wed, 1 Dec 2021 17:50:58 -0500 Subject: [PATCH 247/276] Include legacy AWS S3 CA in the bundle It seems the rules for what AWS keep changing, so best to keep all of the CAs in the bundle. --- .../files/pgo-backrest-repo/aws-s3-ca.crt | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/aws-s3-ca.crt b/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/aws-s3-ca.crt index 31ebd8a6e5..419c8de459 100644 --- a/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/aws-s3-ca.crt +++ b/installers/ansible/roles/pgo-operator/files/pgo-backrest-repo/aws-s3-ca.crt @@ -1,4 +1,25 @@ -----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ +RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD +VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX +DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y +ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy +VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr +mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr +IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK +mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu +XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy +dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye +jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 +BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 +DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 +9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx +jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 +Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz +ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS +R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL From 47d0a9b9317162d60239a5691cc47ef8da5ef3eb Mon Sep 17 00:00:00 2001 From: andrewlecuyer <43458182+andrewlecuyer@users.noreply.github.com> Date: Wed, 1 Dec 2021 20:27:15 -0600 Subject: [PATCH 248/276] Allow Scaledown of Original Primary After Upgrade (#2891) --- cmd/pgo-rmdata/process.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/cmd/pgo-rmdata/process.go b/cmd/pgo-rmdata/process.go index 60b123b84d..16c14cf59b 100644 --- a/cmd/pgo-rmdata/process.go +++ b/cmd/pgo-rmdata/process.go @@ -59,20 +59,16 @@ func Delete(request Request) { log.Error(err) } // delete the pgreplica CRD - if err := request.Clientset. - CrunchydataV1().Pgreplicas(request.Namespace). + if err := request.Clientset.CrunchydataV1().Pgreplicas(request.Namespace). Delete(ctx, request.ReplicaName, metav1.DeleteOptions{}); err != nil { - // If the name of the replica being deleted matches the scope for the cluster, then - // we assume it was the original primary and the pgreplica deletion will fail with - // a not found error. In this case we allow the rmdata process to continue despite - // the error. This allows for the original primary to be scaled down once it is - // is no longer a primary, and has become a replica. - if !(request.ReplicaName == request.ClusterPGHAScope && kerror.IsNotFound(err)) { + // if the pgreplica is not found, assume we're scaling down the original primary and + // continue with removing the replica + if !kerror.IsNotFound(err) { log.Error(err) return + } else { + log.Debug("pgreplica not found, assuming scale down of original primary") } - log.Debug("replica name matches PGHA scope, assuming scale down of original primary" + - "and therefore ignoring error attempting to delete nonexistent pgreplica") } err = removeReplica(request) From b344fda0f97c540af40ef578977aacfc36bce09f Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Wed, 1 Dec 2021 21:57:30 -0500 Subject: [PATCH 249/276] Update 4.6.5 release notes --- docs/content/releases/4.6.5.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/content/releases/4.6.5.md b/docs/content/releases/4.6.5.md index b381d936a8..00614b122e 100644 --- a/docs/content/releases/4.6.5.md +++ b/docs/content/releases/4.6.5.md @@ -24,4 +24,5 @@ PostgreSQL Operator 4.6.5 release includes the following software versions upgra - Ensure the `pgo create pgbouncer` command can set CPU and memory limits via `--cpu-limit` and `--memory-limit` respectively. - Ensure `pgo delete backup` works with backups stored in S3 or GCS. Reported by Munjal Patel (@munjalpatel). -- Update the `aws-s3-ca.crt` value to use the newer CAs provided by AWS. If a PostgreSQL cluster is using the old default CA, PGO will update the general one kept in the `pgo-backrest-repo-config` Secret and `pgo upgrade` will update it for a specific cluster.S +- Update the `aws-s3-ca.crt` value to use the newer CAs provided by AWS. If a PostgreSQL cluster is using the old default CA, PGO will update the general one kept in the `pgo-backrest-repo-config` Secret and `pgo upgrade` will update it for a specific cluster. +- Allow for the original primary instance to be scaled down after running `pgo upgrade`. From 8a0bde5baa536dedfead06885998bb7d72fdbf98 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Wed, 1 Dec 2021 22:13:19 -0500 Subject: [PATCH 250/276] Fix overly ambitious backpatch Or in other words, missed removing a return --- cmd/pgo-rmdata/process.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/pgo-rmdata/process.go b/cmd/pgo-rmdata/process.go index 16c14cf59b..eaeee9369c 100644 --- a/cmd/pgo-rmdata/process.go +++ b/cmd/pgo-rmdata/process.go @@ -65,7 +65,6 @@ func Delete(request Request) { // continue with removing the replica if !kerror.IsNotFound(err) { log.Error(err) - return } else { log.Debug("pgreplica not found, assuming scale down of original primary") } From 4b26bacb1c31f1614d75f9fed3f1e8be1a0f8680 Mon Sep 17 00:00:00 2001 From: "Jonathan S. Katz" Date: Tue, 4 Jan 2022 10:24:00 -0500 Subject: [PATCH 251/276] Update Copyright to 2022 --- LICENSE.md | 2 +- bin/check-deps.sh | 2 +- bin/crunchy-postgres-exporter/common_lib.sh | 2 +- bin/crunchy-postgres-exporter/start.sh | 2 +- bin/get-deps.sh | 2 +- bin/get-pgmonitor.sh | 2 +- bin/license_aggregator.sh | 2 +- bin/pgo-event/pgo-event.sh | 2 +- bin/pgo-rmdata/start.sh | 2 +- bin/pgo-scheduler/start.sh | 2 +- bin/pgo-sqlrunner/start.sh | 2 +- bin/pre-pull-crunchy-containers.sh | 2 +- bin/pull-from-gcr.sh | 2 +- bin/push-ccp-to-gcr.sh | 2 +- bin/push-to-gcr.sh | 2 +- bin/uid_daemon.sh | 2 +- bin/upgrade-secret.sh | 2 +- cmd/apiserver/main.go | 2 +- cmd/pgo-rmdata/main.go | 2 +- cmd/pgo-rmdata/process.go | 2 +- cmd/pgo-rmdata/types.go | 2 +- cmd/pgo-scheduler/main.go | 2 +- cmd/pgo-scheduler/scheduler/configmapcontroller.go | 2 +- cmd/pgo-scheduler/scheduler/controllermanager.go | 2 +- cmd/pgo-scheduler/scheduler/pgbackrest.go | 2 +- cmd/pgo-scheduler/scheduler/policy.go | 2 +- cmd/pgo-scheduler/scheduler/scheduler.go | 2 +- cmd/pgo-scheduler/scheduler/tasks.go | 2 +- cmd/pgo-scheduler/scheduler/types.go | 2 +- cmd/pgo-scheduler/scheduler/validate.go | 2 +- cmd/pgo-scheduler/scheduler/validate_test.go | 2 +- cmd/pgo/api/backrest.go | 2 +- cmd/pgo/api/cat.go | 2 +- cmd/pgo/api/cluster.go | 2 +- cmd/pgo/api/common.go | 2 +- cmd/pgo/api/config.go | 2 +- cmd/pgo/api/df.go | 2 +- cmd/pgo/api/failover.go | 2 +- cmd/pgo/api/label.go | 2 +- cmd/pgo/api/namespace.go | 2 +- cmd/pgo/api/pgadmin.go | 2 +- cmd/pgo/api/pgbouncer.go | 2 +- cmd/pgo/api/pgdump.go | 2 +- cmd/pgo/api/pgorole.go | 2 +- cmd/pgo/api/pgouser.go | 2 +- cmd/pgo/api/policy.go | 2 +- cmd/pgo/api/pvc.go | 2 +- cmd/pgo/api/reload.go | 2 +- cmd/pgo/api/restart.go | 2 +- cmd/pgo/api/restore.go | 2 +- cmd/pgo/api/restoreDump.go | 2 +- cmd/pgo/api/scale.go | 2 +- cmd/pgo/api/scaledown.go | 2 +- cmd/pgo/api/schedule.go | 2 +- cmd/pgo/api/status.go | 2 +- cmd/pgo/api/test.go | 2 +- cmd/pgo/api/upgrade.go | 2 +- cmd/pgo/api/user.go | 2 +- cmd/pgo/api/version.go | 2 +- cmd/pgo/api/workflow.go | 2 +- cmd/pgo/cmd/auth.go | 2 +- cmd/pgo/cmd/backrest.go | 2 +- cmd/pgo/cmd/backup.go | 2 +- cmd/pgo/cmd/cat.go | 2 +- cmd/pgo/cmd/cluster.go | 2 +- cmd/pgo/cmd/common.go | 2 +- cmd/pgo/cmd/config.go | 2 +- cmd/pgo/cmd/create.go | 2 +- cmd/pgo/cmd/delete.go | 2 +- cmd/pgo/cmd/df.go | 2 +- cmd/pgo/cmd/failover.go | 2 +- cmd/pgo/cmd/flags.go | 2 +- cmd/pgo/cmd/label.go | 2 +- cmd/pgo/cmd/namespace.go | 2 +- cmd/pgo/cmd/pgadmin.go | 2 +- cmd/pgo/cmd/pgbouncer.go | 2 +- cmd/pgo/cmd/pgdump.go | 2 +- cmd/pgo/cmd/pgorole.go | 2 +- cmd/pgo/cmd/pgouser.go | 2 +- cmd/pgo/cmd/policy.go | 2 +- cmd/pgo/cmd/pvc.go | 2 +- cmd/pgo/cmd/reload.go | 2 +- cmd/pgo/cmd/restart.go | 2 +- cmd/pgo/cmd/restore.go | 2 +- cmd/pgo/cmd/root.go | 2 +- cmd/pgo/cmd/scale.go | 2 +- cmd/pgo/cmd/scaledown.go | 2 +- cmd/pgo/cmd/schedule.go | 2 +- cmd/pgo/cmd/show.go | 2 +- cmd/pgo/cmd/status.go | 2 +- cmd/pgo/cmd/test.go | 2 +- cmd/pgo/cmd/update.go | 2 +- cmd/pgo/cmd/upgrade.go | 2 +- cmd/pgo/cmd/user.go | 2 +- cmd/pgo/cmd/version.go | 2 +- cmd/pgo/cmd/watch.go | 2 +- cmd/pgo/cmd/workflow.go | 2 +- cmd/pgo/generatedocs.go | 2 +- cmd/pgo/main.go | 2 +- cmd/pgo/util/confirmation.go | 2 +- cmd/pgo/util/pad.go | 2 +- cmd/pgo/util/validation.go | 2 +- cmd/postgres-operator/main.go | 2 +- cmd/postgres-operator/open_telemetry.go | 2 +- deploy/add-targeted-namespace-reconcile-rbac.sh | 2 +- deploy/add-targeted-namespace.sh | 2 +- deploy/cleannamespaces.sh | 2 +- deploy/cleanup-rbac.sh | 2 +- deploy/cleanup.sh | 2 +- deploy/deploy.sh | 2 +- deploy/gen-api-keys.sh | 2 +- deploy/install-bootstrap-creds.sh | 2 +- deploy/install-rbac.sh | 2 +- deploy/remove-crd.sh | 2 +- deploy/setupnamespaces.sh | 2 +- deploy/show-crd.sh | 2 +- deploy/upgrade-creds.sh | 2 +- deploy/upgrade-pgo.sh | 2 +- docs/layouts/partials/flex/body-aftercontent.html | 2 +- examples/create-by-resource/run.sh | 2 +- examples/custom-config/create.sh | 2 +- examples/custom-config/setup.sql | 2 +- hack/boilerplate.go.txt | 2 +- hack/config_sync.sh | 2 +- hack/update-codegen.sh | 2 +- .../roles/pgo-operator/templates/add-targeted-namespace.sh.j2 | 2 +- installers/image/bin/pgo-deploy.sh | 2 +- installers/kubectl/client-setup.sh | 2 +- internal/apiserver/backrestservice/backrestimpl.go | 2 +- internal/apiserver/backrestservice/backrestservice.go | 2 +- internal/apiserver/backupoptions/backupoptionsutil.go | 2 +- internal/apiserver/backupoptions/backupoptionsutil_test.go | 2 +- internal/apiserver/backupoptions/pgbackrestoptions.go | 2 +- internal/apiserver/backupoptions/pgdumpoptions.go | 2 +- internal/apiserver/catservice/catimpl.go | 2 +- internal/apiserver/catservice/catservice.go | 2 +- internal/apiserver/clusterservice/clusterimpl.go | 2 +- internal/apiserver/clusterservice/clusterservice.go | 2 +- internal/apiserver/clusterservice/scaleimpl.go | 2 +- internal/apiserver/clusterservice/scaleservice.go | 2 +- internal/apiserver/common.go | 2 +- internal/apiserver/common_test.go | 2 +- internal/apiserver/configservice/configimpl.go | 2 +- internal/apiserver/configservice/configservice.go | 2 +- internal/apiserver/dfservice/dfimpl.go | 2 +- internal/apiserver/dfservice/dfservice.go | 2 +- internal/apiserver/failoverservice/failoverimpl.go | 2 +- internal/apiserver/failoverservice/failoverservice.go | 2 +- internal/apiserver/labelservice/labelimpl.go | 2 +- internal/apiserver/labelservice/labelservice.go | 2 +- internal/apiserver/middleware.go | 2 +- internal/apiserver/namespaceservice/namespaceimpl.go | 2 +- internal/apiserver/namespaceservice/namespaceservice.go | 2 +- internal/apiserver/perms.go | 2 +- internal/apiserver/pgadminservice/pgadminimpl.go | 2 +- internal/apiserver/pgadminservice/pgadminservice.go | 2 +- internal/apiserver/pgbouncerservice/pgbouncerimpl.go | 2 +- internal/apiserver/pgbouncerservice/pgbouncerservice.go | 2 +- internal/apiserver/pgdumpservice/pgdumpimpl.go | 2 +- internal/apiserver/pgdumpservice/pgdumpservice.go | 2 +- internal/apiserver/pgoroleservice/pgoroleimpl.go | 2 +- internal/apiserver/pgoroleservice/pgoroleservice.go | 2 +- internal/apiserver/pgouserservice/pgouserimpl.go | 2 +- internal/apiserver/pgouserservice/pgouserservice.go | 2 +- internal/apiserver/policyservice/policyimpl.go | 2 +- internal/apiserver/policyservice/policyservice.go | 2 +- internal/apiserver/pvcservice/pvcimpl.go | 2 +- internal/apiserver/pvcservice/pvcservice.go | 2 +- internal/apiserver/reloadservice/reloadimpl.go | 2 +- internal/apiserver/reloadservice/reloadservice.go | 2 +- internal/apiserver/restartservice/restartimpl.go | 2 +- internal/apiserver/restartservice/restartservice.go | 2 +- internal/apiserver/root.go | 2 +- internal/apiserver/routing/doc.go | 2 +- internal/apiserver/routing/routes.go | 2 +- internal/apiserver/scheduleservice/scheduleimpl.go | 2 +- internal/apiserver/scheduleservice/scheduleservice.go | 2 +- internal/apiserver/statusservice/statusimpl.go | 2 +- internal/apiserver/statusservice/statusservice.go | 2 +- internal/apiserver/upgradeservice/upgradeimpl.go | 2 +- internal/apiserver/upgradeservice/upgradeservice.go | 2 +- internal/apiserver/userservice/userimpl.go | 2 +- internal/apiserver/userservice/userimpl_test.go | 2 +- internal/apiserver/userservice/userservice.go | 2 +- internal/apiserver/versionservice/versionimpl.go | 2 +- internal/apiserver/versionservice/versionservice.go | 2 +- internal/apiserver/workflowservice/workflowimpl.go | 2 +- internal/apiserver/workflowservice/workflowservice.go | 2 +- internal/config/annotations.go | 2 +- internal/config/defaults.go | 2 +- internal/config/images.go | 2 +- internal/config/labels.go | 2 +- internal/config/pgoconfig.go | 2 +- internal/config/secrets.go | 2 +- internal/config/volumes.go | 2 +- internal/controller/configmap/configmapcontroller.go | 2 +- internal/controller/configmap/synchandler.go | 2 +- internal/controller/controllerutil.go | 2 +- internal/controller/job/backresthandler.go | 2 +- internal/controller/job/bootstraphandler.go | 2 +- internal/controller/job/jobcontroller.go | 2 +- internal/controller/job/jobevents.go | 2 +- internal/controller/job/jobutil.go | 2 +- internal/controller/job/pgdumphandler.go | 2 +- internal/controller/job/rmdatahandler.go | 2 +- internal/controller/manager/controllermanager.go | 2 +- internal/controller/manager/rbac.go | 2 +- internal/controller/namespace/namespacecontroller.go | 2 +- internal/controller/pgcluster/pgclustercontroller.go | 2 +- internal/controller/pgpolicy/pgpolicycontroller.go | 2 +- internal/controller/pgreplica/pgreplicacontroller.go | 2 +- internal/controller/pgtask/backresthandler.go | 2 +- internal/controller/pgtask/pgtaskcontroller.go | 2 +- internal/controller/pod/inithandler.go | 2 +- internal/controller/pod/podcontroller.go | 2 +- internal/controller/pod/podevents.go | 2 +- internal/controller/pod/promotionhandler.go | 2 +- internal/kubeapi/client_config.go | 2 +- internal/kubeapi/endpoints.go | 2 +- internal/kubeapi/errors.go | 2 +- internal/kubeapi/exec.go | 2 +- internal/kubeapi/fake/clientset.go | 2 +- internal/kubeapi/fake/fakeclients.go | 2 +- internal/kubeapi/patch.go | 2 +- internal/kubeapi/patch_test.go | 2 +- internal/kubeapi/volumes.go | 2 +- internal/kubeapi/volumes_test.go | 2 +- internal/logging/loglib.go | 2 +- internal/ns/nslogic.go | 2 +- internal/operator/backrest/backup.go | 2 +- internal/operator/backrest/repo.go | 2 +- internal/operator/backrest/restore.go | 2 +- internal/operator/backrest/stanza.go | 2 +- internal/operator/cluster/cluster.go | 2 +- internal/operator/cluster/clusterlogic.go | 2 +- internal/operator/cluster/common.go | 2 +- internal/operator/cluster/common_test.go | 2 +- internal/operator/cluster/exporter.go | 2 +- internal/operator/cluster/pgadmin.go | 2 +- internal/operator/cluster/pgbadger.go | 2 +- internal/operator/cluster/pgbouncer.go | 2 +- internal/operator/cluster/pgbouncer_test.go | 2 +- internal/operator/cluster/rolling.go | 2 +- internal/operator/cluster/service.go | 2 +- internal/operator/cluster/standby.go | 2 +- internal/operator/cluster/upgrade.go | 2 +- internal/operator/clusterutilities.go | 2 +- internal/operator/clusterutilities_test.go | 2 +- internal/operator/common.go | 2 +- internal/operator/common_test.go | 2 +- internal/operator/config/configutil.go | 2 +- internal/operator/config/dcs.go | 2 +- internal/operator/config/localdb.go | 2 +- internal/operator/failover.go | 2 +- internal/operator/failover_test.go | 2 +- internal/operator/operatorupgrade/version-check.go | 2 +- internal/operator/pgbackrest.go | 2 +- internal/operator/pgbackrest_test.go | 2 +- internal/operator/pgdump/dump.go | 2 +- internal/operator/pgdump/restore.go | 2 +- internal/operator/pvc/pvc.go | 2 +- internal/operator/storage.go | 2 +- internal/operator/storage_test.go | 2 +- internal/operator/switchover.go | 2 +- internal/operator/switchover_test.go | 2 +- internal/operator/task/applypolicies.go | 2 +- internal/operator/task/rmdata.go | 2 +- internal/operator/task/workflow.go | 2 +- internal/operator/wal.go | 2 +- internal/patroni/doc.go | 2 +- internal/patroni/patroni.go | 2 +- internal/pgadmin/backoff.go | 2 +- internal/pgadmin/backoff_test.go | 2 +- internal/pgadmin/crypto.go | 2 +- internal/pgadmin/crypto_test.go | 2 +- internal/pgadmin/doc.go | 2 +- internal/pgadmin/hash.go | 2 +- internal/pgadmin/logic.go | 2 +- internal/pgadmin/runner.go | 2 +- internal/pgadmin/server.go | 2 +- internal/postgres/doc.go | 2 +- internal/postgres/password/doc.go | 2 +- internal/postgres/password/md5.go | 2 +- internal/postgres/password/md5_test.go | 2 +- internal/postgres/password/password.go | 2 +- internal/postgres/password/password_test.go | 2 +- internal/postgres/password/scram.go | 2 +- internal/postgres/password/scram_test.go | 2 +- internal/tlsutil/primitives.go | 2 +- internal/tlsutil/primitives_test.go | 2 +- internal/util/backrest.go | 2 +- internal/util/cluster.go | 2 +- internal/util/cluster_test.go | 2 +- internal/util/exporter.go | 2 +- internal/util/exporter_test.go | 2 +- internal/util/failover.go | 2 +- internal/util/pgbouncer.go | 2 +- internal/util/policy.go | 2 +- internal/util/secrets.go | 2 +- internal/util/secrets_test.go | 2 +- internal/util/ssh.go | 2 +- internal/util/util.go | 2 +- pkg/apis/crunchydata.com/v1/cluster.go | 2 +- pkg/apis/crunchydata.com/v1/cluster_test.go | 2 +- pkg/apis/crunchydata.com/v1/common.go | 2 +- pkg/apis/crunchydata.com/v1/common_test.go | 2 +- pkg/apis/crunchydata.com/v1/doc.go | 2 +- pkg/apis/crunchydata.com/v1/errors.go | 2 +- pkg/apis/crunchydata.com/v1/policy.go | 2 +- pkg/apis/crunchydata.com/v1/register.go | 2 +- pkg/apis/crunchydata.com/v1/replica.go | 2 +- pkg/apis/crunchydata.com/v1/task.go | 2 +- pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go | 2 +- pkg/apiservermsgs/backrestmsgs.go | 2 +- pkg/apiservermsgs/catmsgs.go | 2 +- pkg/apiservermsgs/clustermsgs.go | 2 +- pkg/apiservermsgs/common.go | 2 +- pkg/apiservermsgs/configmsgs.go | 2 +- pkg/apiservermsgs/dfmsgs.go | 2 +- pkg/apiservermsgs/failovermsgs.go | 2 +- pkg/apiservermsgs/labelmsgs.go | 2 +- pkg/apiservermsgs/namespacemsgs.go | 2 +- pkg/apiservermsgs/pgadminmsgs.go | 2 +- pkg/apiservermsgs/pgbouncermsgs.go | 2 +- pkg/apiservermsgs/pgdumpmsgs.go | 2 +- pkg/apiservermsgs/pgorolemsgs.go | 2 +- pkg/apiservermsgs/pgousermsgs.go | 2 +- pkg/apiservermsgs/policymsgs.go | 2 +- pkg/apiservermsgs/pvcmsgs.go | 2 +- pkg/apiservermsgs/reloadmsgs.go | 2 +- pkg/apiservermsgs/restartmsgs.go | 2 +- pkg/apiservermsgs/schedulemsgs.go | 2 +- pkg/apiservermsgs/statusmsgs.go | 2 +- pkg/apiservermsgs/upgrademsgs.go | 2 +- pkg/apiservermsgs/usermsgs.go | 2 +- pkg/apiservermsgs/versionmsgs.go | 2 +- pkg/apiservermsgs/watchmsgs.go | 2 +- pkg/apiservermsgs/workflowmsgs.go | 2 +- pkg/events/eventing.go | 2 +- pkg/events/eventtype.go | 2 +- pkg/events/pgoeventtype.go | 2 +- pkg/generated/clientset/versioned/clientset.go | 2 +- pkg/generated/clientset/versioned/doc.go | 2 +- pkg/generated/clientset/versioned/fake/clientset_generated.go | 2 +- pkg/generated/clientset/versioned/fake/doc.go | 2 +- pkg/generated/clientset/versioned/fake/register.go | 2 +- pkg/generated/clientset/versioned/scheme/doc.go | 2 +- pkg/generated/clientset/versioned/scheme/register.go | 2 +- .../typed/crunchydata.com/v1/crunchydata.com_client.go | 2 +- .../clientset/versioned/typed/crunchydata.com/v1/doc.go | 2 +- .../clientset/versioned/typed/crunchydata.com/v1/fake/doc.go | 2 +- .../crunchydata.com/v1/fake/fake_crunchydata.com_client.go | 2 +- .../versioned/typed/crunchydata.com/v1/fake/fake_pgcluster.go | 2 +- .../versioned/typed/crunchydata.com/v1/fake/fake_pgpolicy.go | 2 +- .../versioned/typed/crunchydata.com/v1/fake/fake_pgreplica.go | 2 +- .../versioned/typed/crunchydata.com/v1/fake/fake_pgtask.go | 2 +- .../versioned/typed/crunchydata.com/v1/generated_expansion.go | 2 +- .../clientset/versioned/typed/crunchydata.com/v1/pgcluster.go | 2 +- .../clientset/versioned/typed/crunchydata.com/v1/pgpolicy.go | 2 +- .../clientset/versioned/typed/crunchydata.com/v1/pgreplica.go | 2 +- .../clientset/versioned/typed/crunchydata.com/v1/pgtask.go | 2 +- .../informers/externalversions/crunchydata.com/interface.go | 2 +- .../informers/externalversions/crunchydata.com/v1/interface.go | 2 +- .../informers/externalversions/crunchydata.com/v1/pgcluster.go | 2 +- .../informers/externalversions/crunchydata.com/v1/pgpolicy.go | 2 +- .../informers/externalversions/crunchydata.com/v1/pgreplica.go | 2 +- .../informers/externalversions/crunchydata.com/v1/pgtask.go | 2 +- pkg/generated/informers/externalversions/factory.go | 2 +- pkg/generated/informers/externalversions/generic.go | 2 +- .../externalversions/internalinterfaces/factory_interfaces.go | 2 +- pkg/generated/listers/crunchydata.com/v1/expansion_generated.go | 2 +- pkg/generated/listers/crunchydata.com/v1/pgcluster.go | 2 +- pkg/generated/listers/crunchydata.com/v1/pgpolicy.go | 2 +- pkg/generated/listers/crunchydata.com/v1/pgreplica.go | 2 +- pkg/generated/listers/crunchydata.com/v1/pgtask.go | 2 +- pv/create-pv-nfs-label.sh | 2 +- pv/create-pv-nfs-legacy.sh | 2 +- pv/create-pv-nfs.sh | 2 +- pv/create-pv.sh | 2 +- pv/delete-pv.sh | 2 +- testing/pgo_cli/cluster_annotation_test.go | 2 +- testing/pgo_cli/cluster_backup_test.go | 2 +- testing/pgo_cli/cluster_cat_test.go | 2 +- testing/pgo_cli/cluster_create_test.go | 2 +- testing/pgo_cli/cluster_delete_test.go | 2 +- testing/pgo_cli/cluster_df_test.go | 2 +- testing/pgo_cli/cluster_failover_test.go | 2 +- testing/pgo_cli/cluster_label_test.go | 2 +- testing/pgo_cli/cluster_pgbouncer_test.go | 2 +- testing/pgo_cli/cluster_policy_test.go | 2 +- testing/pgo_cli/cluster_pvc_test.go | 2 +- testing/pgo_cli/cluster_reload_test.go | 2 +- testing/pgo_cli/cluster_restart_test.go | 2 +- testing/pgo_cli/cluster_scale_test.go | 2 +- testing/pgo_cli/cluster_scaledown_test.go | 2 +- testing/pgo_cli/cluster_test_test.go | 2 +- testing/pgo_cli/cluster_user_test.go | 2 +- testing/pgo_cli/operator_namespace_test.go | 2 +- testing/pgo_cli/operator_rbac_test.go | 2 +- testing/pgo_cli/operator_test.go | 2 +- testing/pgo_cli/suite_helpers_test.go | 2 +- testing/pgo_cli/suite_pgo_cmd_test.go | 2 +- testing/pgo_cli/suite_test.go | 2 +- 403 files changed, 403 insertions(+), 403 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index f8ebe3dacd..79e1438a7d 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -176,7 +176,7 @@ END OF TERMS AND CONDITIONS - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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/bin/check-deps.sh b/bin/check-deps.sh index ba66ec9f1d..819756afa8 100755 --- a/bin/check-deps.sh +++ b/bin/check-deps.sh @@ -1,6 +1,6 @@ #!/bin/bash -e -# Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2020 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/crunchy-postgres-exporter/common_lib.sh b/bin/crunchy-postgres-exporter/common_lib.sh index a42a618eb1..5dd828322c 100755 --- a/bin/crunchy-postgres-exporter/common_lib.sh +++ b/bin/crunchy-postgres-exporter/common_lib.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/crunchy-postgres-exporter/start.sh b/bin/crunchy-postgres-exporter/start.sh index d53bbb1658..1ea756e95b 100755 --- a/bin/crunchy-postgres-exporter/start.sh +++ b/bin/crunchy-postgres-exporter/start.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/get-deps.sh b/bin/get-deps.sh index 0b895f329d..2108ef5231 100755 --- a/bin/get-deps.sh +++ b/bin/get-deps.sh @@ -1,6 +1,6 @@ #!/bin/bash -e -# Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/get-pgmonitor.sh b/bin/get-pgmonitor.sh index f48d842399..5bf97d1475 100755 --- a/bin/get-pgmonitor.sh +++ b/bin/get-pgmonitor.sh @@ -1,6 +1,6 @@ #!/bin/bash -e -# Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/license_aggregator.sh b/bin/license_aggregator.sh index c16070cfa7..e4ee6d3123 100755 --- a/bin/license_aggregator.sh +++ b/bin/license_aggregator.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2021 Crunchy Data Solutions, Inc. +# Copyright 2021 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/pgo-event/pgo-event.sh b/bin/pgo-event/pgo-event.sh index 1602e56193..7e16d5f3f1 100755 --- a/bin/pgo-event/pgo-event.sh +++ b/bin/pgo-event/pgo-event.sh @@ -1,6 +1,6 @@ #!/bin/bash -x -# Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/pgo-rmdata/start.sh b/bin/pgo-rmdata/start.sh index 228f891694..bf9dd9f9c2 100755 --- a/bin/pgo-rmdata/start.sh +++ b/bin/pgo-rmdata/start.sh @@ -1,6 +1,6 @@ #!/bin/bash -x -# Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/pgo-scheduler/start.sh b/bin/pgo-scheduler/start.sh index 4549d82d57..758a25bbd3 100755 --- a/bin/pgo-scheduler/start.sh +++ b/bin/pgo-scheduler/start.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/pgo-sqlrunner/start.sh b/bin/pgo-sqlrunner/start.sh index 01422d26d7..8a272855f1 100755 --- a/bin/pgo-sqlrunner/start.sh +++ b/bin/pgo-sqlrunner/start.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/pre-pull-crunchy-containers.sh b/bin/pre-pull-crunchy-containers.sh index 91cfcb9dc8..2726b2e02d 100755 --- a/bin/pre-pull-crunchy-containers.sh +++ b/bin/pre-pull-crunchy-containers.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/pull-from-gcr.sh b/bin/pull-from-gcr.sh index 2087443de0..d0627dc343 100755 --- a/bin/pull-from-gcr.sh +++ b/bin/pull-from-gcr.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/push-ccp-to-gcr.sh b/bin/push-ccp-to-gcr.sh index 4d0460e51b..5780b8cd45 100755 --- a/bin/push-ccp-to-gcr.sh +++ b/bin/push-ccp-to-gcr.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/push-to-gcr.sh b/bin/push-to-gcr.sh index cd21a868f4..7bfff6b5bb 100755 --- a/bin/push-to-gcr.sh +++ b/bin/push-to-gcr.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/uid_daemon.sh b/bin/uid_daemon.sh index bc988bae79..58f8fd7505 100755 --- a/bin/uid_daemon.sh +++ b/bin/uid_daemon.sh @@ -1,6 +1,6 @@ #!/usr/bin/bash -# Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2020 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/upgrade-secret.sh b/bin/upgrade-secret.sh index f852008890..b52b751727 100755 --- a/bin/upgrade-secret.sh +++ b/bin/upgrade-secret.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/cmd/apiserver/main.go b/cmd/apiserver/main.go index 562a5870a8..df97046cb7 100644 --- a/cmd/apiserver/main.go +++ b/cmd/apiserver/main.go @@ -1,7 +1,7 @@ package main /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-rmdata/main.go b/cmd/pgo-rmdata/main.go index 1b3b4d82cf..13a9b19d94 100644 --- a/cmd/pgo-rmdata/main.go +++ b/cmd/pgo-rmdata/main.go @@ -1,7 +1,7 @@ package main /* -Copyright 2019 - 2021 Crunchy Data +Copyright 2019 - 2022 Crunchy Data 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 diff --git a/cmd/pgo-rmdata/process.go b/cmd/pgo-rmdata/process.go index eaeee9369c..c17e34dd17 100644 --- a/cmd/pgo-rmdata/process.go +++ b/cmd/pgo-rmdata/process.go @@ -1,7 +1,7 @@ package main /* -Copyright 2019 - 2021 Crunchy Data +Copyright 2019 - 2022 Crunchy Data 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 diff --git a/cmd/pgo-rmdata/types.go b/cmd/pgo-rmdata/types.go index 590bf9ac13..a6055d3948 100644 --- a/cmd/pgo-rmdata/types.go +++ b/cmd/pgo-rmdata/types.go @@ -1,7 +1,7 @@ package main /* -Copyright 2019 - 2021 Crunchy Data +Copyright 2019 - 2022 Crunchy Data 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 diff --git a/cmd/pgo-scheduler/main.go b/cmd/pgo-scheduler/main.go index ef13e0e794..0e7268a2c2 100644 --- a/cmd/pgo-scheduler/main.go +++ b/cmd/pgo-scheduler/main.go @@ -1,7 +1,7 @@ package main /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/scheduler/configmapcontroller.go b/cmd/pgo-scheduler/scheduler/configmapcontroller.go index dfb6f7aaa7..e3c6444710 100644 --- a/cmd/pgo-scheduler/scheduler/configmapcontroller.go +++ b/cmd/pgo-scheduler/scheduler/configmapcontroller.go @@ -1,7 +1,7 @@ package scheduler /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/scheduler/controllermanager.go b/cmd/pgo-scheduler/scheduler/controllermanager.go index fa9244b0d4..11fc741242 100644 --- a/cmd/pgo-scheduler/scheduler/controllermanager.go +++ b/cmd/pgo-scheduler/scheduler/controllermanager.go @@ -1,7 +1,7 @@ package scheduler /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/scheduler/pgbackrest.go b/cmd/pgo-scheduler/scheduler/pgbackrest.go index 930d82859d..ba34428abf 100644 --- a/cmd/pgo-scheduler/scheduler/pgbackrest.go +++ b/cmd/pgo-scheduler/scheduler/pgbackrest.go @@ -1,7 +1,7 @@ package scheduler /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/scheduler/policy.go b/cmd/pgo-scheduler/scheduler/policy.go index 00d92b9225..114a3c6c4e 100644 --- a/cmd/pgo-scheduler/scheduler/policy.go +++ b/cmd/pgo-scheduler/scheduler/policy.go @@ -1,7 +1,7 @@ package scheduler /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/scheduler/scheduler.go b/cmd/pgo-scheduler/scheduler/scheduler.go index 18d49b4ebd..554fd0bca2 100644 --- a/cmd/pgo-scheduler/scheduler/scheduler.go +++ b/cmd/pgo-scheduler/scheduler/scheduler.go @@ -1,7 +1,7 @@ package scheduler /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/scheduler/tasks.go b/cmd/pgo-scheduler/scheduler/tasks.go index a8374bfbe7..a79a60ba7f 100644 --- a/cmd/pgo-scheduler/scheduler/tasks.go +++ b/cmd/pgo-scheduler/scheduler/tasks.go @@ -1,7 +1,7 @@ package scheduler /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/scheduler/types.go b/cmd/pgo-scheduler/scheduler/types.go index 75a5b297b3..8850e3ebc7 100644 --- a/cmd/pgo-scheduler/scheduler/types.go +++ b/cmd/pgo-scheduler/scheduler/types.go @@ -1,7 +1,7 @@ package scheduler /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/scheduler/validate.go b/cmd/pgo-scheduler/scheduler/validate.go index d483688097..660b6f381d 100644 --- a/cmd/pgo-scheduler/scheduler/validate.go +++ b/cmd/pgo-scheduler/scheduler/validate.go @@ -1,7 +1,7 @@ package scheduler /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/scheduler/validate_test.go b/cmd/pgo-scheduler/scheduler/validate_test.go index d6cb8f2cb4..ccf9b24334 100644 --- a/cmd/pgo-scheduler/scheduler/validate_test.go +++ b/cmd/pgo-scheduler/scheduler/validate_test.go @@ -1,7 +1,7 @@ package scheduler /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/backrest.go b/cmd/pgo/api/backrest.go index f71633c654..0a36376a7d 100644 --- a/cmd/pgo/api/backrest.go +++ b/cmd/pgo/api/backrest.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/cat.go b/cmd/pgo/api/cat.go index 8c19125034..d6abdffe75 100644 --- a/cmd/pgo/api/cat.go +++ b/cmd/pgo/api/cat.go @@ -1,7 +1,7 @@ package api /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/cluster.go b/cmd/pgo/api/cluster.go index 904f09bfe3..6471ccc5cd 100644 --- a/cmd/pgo/api/cluster.go +++ b/cmd/pgo/api/cluster.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/common.go b/cmd/pgo/api/common.go index 970691e516..c990bcc93c 100644 --- a/cmd/pgo/api/common.go +++ b/cmd/pgo/api/common.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/config.go b/cmd/pgo/api/config.go index 375a8186c5..9e5370ec5d 100644 --- a/cmd/pgo/api/config.go +++ b/cmd/pgo/api/config.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/df.go b/cmd/pgo/api/df.go index 0d5215fa62..9a7ca77ecf 100644 --- a/cmd/pgo/api/df.go +++ b/cmd/pgo/api/df.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/failover.go b/cmd/pgo/api/failover.go index 15c93c32fd..668e74d517 100644 --- a/cmd/pgo/api/failover.go +++ b/cmd/pgo/api/failover.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/label.go b/cmd/pgo/api/label.go index b94f943abf..b48aabbbc2 100644 --- a/cmd/pgo/api/label.go +++ b/cmd/pgo/api/label.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/namespace.go b/cmd/pgo/api/namespace.go index 038b705557..939e7a7608 100644 --- a/cmd/pgo/api/namespace.go +++ b/cmd/pgo/api/namespace.go @@ -1,7 +1,7 @@ package api /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/pgadmin.go b/cmd/pgo/api/pgadmin.go index e22c09c1b6..cb015f5fef 100644 --- a/cmd/pgo/api/pgadmin.go +++ b/cmd/pgo/api/pgadmin.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/pgbouncer.go b/cmd/pgo/api/pgbouncer.go index 2aca81d5d3..d58ab3f255 100644 --- a/cmd/pgo/api/pgbouncer.go +++ b/cmd/pgo/api/pgbouncer.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/pgdump.go b/cmd/pgo/api/pgdump.go index 5bc5ab118f..1fc7d4b359 100644 --- a/cmd/pgo/api/pgdump.go +++ b/cmd/pgo/api/pgdump.go @@ -1,7 +1,7 @@ package api /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/pgorole.go b/cmd/pgo/api/pgorole.go index 345a18a372..8f28928432 100644 --- a/cmd/pgo/api/pgorole.go +++ b/cmd/pgo/api/pgorole.go @@ -1,7 +1,7 @@ package api /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/pgouser.go b/cmd/pgo/api/pgouser.go index ed2932297d..06904ecde9 100644 --- a/cmd/pgo/api/pgouser.go +++ b/cmd/pgo/api/pgouser.go @@ -1,7 +1,7 @@ package api /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/policy.go b/cmd/pgo/api/policy.go index 9761b87ac3..17196b5c9f 100644 --- a/cmd/pgo/api/policy.go +++ b/cmd/pgo/api/policy.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/pvc.go b/cmd/pgo/api/pvc.go index 2c021b2957..fffd7bbe5d 100644 --- a/cmd/pgo/api/pvc.go +++ b/cmd/pgo/api/pvc.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/reload.go b/cmd/pgo/api/reload.go index 077764848e..c39030f289 100644 --- a/cmd/pgo/api/reload.go +++ b/cmd/pgo/api/reload.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/restart.go b/cmd/pgo/api/restart.go index 7a53e87de0..8a999f032f 100644 --- a/cmd/pgo/api/restart.go +++ b/cmd/pgo/api/restart.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/restore.go b/cmd/pgo/api/restore.go index 03f04876e9..e62391ff43 100644 --- a/cmd/pgo/api/restore.go +++ b/cmd/pgo/api/restore.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/restoreDump.go b/cmd/pgo/api/restoreDump.go index ad4566b4a7..ae78d2a18b 100644 --- a/cmd/pgo/api/restoreDump.go +++ b/cmd/pgo/api/restoreDump.go @@ -1,7 +1,7 @@ package api /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/scale.go b/cmd/pgo/api/scale.go index 6ae78dbf67..17b41ddfff 100644 --- a/cmd/pgo/api/scale.go +++ b/cmd/pgo/api/scale.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/scaledown.go b/cmd/pgo/api/scaledown.go index 4abbe638b7..e7ce4fcbc1 100644 --- a/cmd/pgo/api/scaledown.go +++ b/cmd/pgo/api/scaledown.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/schedule.go b/cmd/pgo/api/schedule.go index e2700a4b19..3967a7f949 100644 --- a/cmd/pgo/api/schedule.go +++ b/cmd/pgo/api/schedule.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/status.go b/cmd/pgo/api/status.go index 4feb1432f2..6a9d1712fd 100644 --- a/cmd/pgo/api/status.go +++ b/cmd/pgo/api/status.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/test.go b/cmd/pgo/api/test.go index f21d6ea919..3270695d8f 100644 --- a/cmd/pgo/api/test.go +++ b/cmd/pgo/api/test.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/upgrade.go b/cmd/pgo/api/upgrade.go index bb780d99bf..e273defcb3 100644 --- a/cmd/pgo/api/upgrade.go +++ b/cmd/pgo/api/upgrade.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/user.go b/cmd/pgo/api/user.go index 48987b3561..56d1928648 100644 --- a/cmd/pgo/api/user.go +++ b/cmd/pgo/api/user.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/version.go b/cmd/pgo/api/version.go index 4499c72c93..e537a335a3 100644 --- a/cmd/pgo/api/version.go +++ b/cmd/pgo/api/version.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/workflow.go b/cmd/pgo/api/workflow.go index e3cf233062..212a5bc3cb 100644 --- a/cmd/pgo/api/workflow.go +++ b/cmd/pgo/api/workflow.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/auth.go b/cmd/pgo/cmd/auth.go index a54dbd9db9..1b0413e6c8 100644 --- a/cmd/pgo/cmd/auth.go +++ b/cmd/pgo/cmd/auth.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/backrest.go b/cmd/pgo/cmd/backrest.go index 7d750e1c6b..aace9b79f7 100644 --- a/cmd/pgo/cmd/backrest.go +++ b/cmd/pgo/cmd/backrest.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/backup.go b/cmd/pgo/cmd/backup.go index 72a3cc85c0..3eece26fa5 100644 --- a/cmd/pgo/cmd/backup.go +++ b/cmd/pgo/cmd/backup.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/cat.go b/cmd/pgo/cmd/cat.go index 63d0d7e6f6..fb3007c2dc 100644 --- a/cmd/pgo/cmd/cat.go +++ b/cmd/pgo/cmd/cat.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/cluster.go b/cmd/pgo/cmd/cluster.go index 7d95f782ff..31ecda6faf 100644 --- a/cmd/pgo/cmd/cluster.go +++ b/cmd/pgo/cmd/cluster.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/common.go b/cmd/pgo/cmd/common.go index 66b1f4bd51..cdd4f8e97b 100644 --- a/cmd/pgo/cmd/common.go +++ b/cmd/pgo/cmd/common.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/config.go b/cmd/pgo/cmd/config.go index b5b34f4c29..a9edd4e024 100644 --- a/cmd/pgo/cmd/config.go +++ b/cmd/pgo/cmd/config.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/create.go b/cmd/pgo/cmd/create.go index 217ba5f2eb..94b8e01d8b 100644 --- a/cmd/pgo/cmd/create.go +++ b/cmd/pgo/cmd/create.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/delete.go b/cmd/pgo/cmd/delete.go index 1d670697af..59417f78ff 100644 --- a/cmd/pgo/cmd/delete.go +++ b/cmd/pgo/cmd/delete.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/df.go b/cmd/pgo/cmd/df.go index c07762ef7b..4247eb040b 100644 --- a/cmd/pgo/cmd/df.go +++ b/cmd/pgo/cmd/df.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/failover.go b/cmd/pgo/cmd/failover.go index b6eb41e735..f791e208a9 100644 --- a/cmd/pgo/cmd/failover.go +++ b/cmd/pgo/cmd/failover.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/flags.go b/cmd/pgo/cmd/flags.go index a91b3daa14..c6ced86ca2 100644 --- a/cmd/pgo/cmd/flags.go +++ b/cmd/pgo/cmd/flags.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/label.go b/cmd/pgo/cmd/label.go index fcda3b7c82..de3f06298a 100644 --- a/cmd/pgo/cmd/label.go +++ b/cmd/pgo/cmd/label.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/namespace.go b/cmd/pgo/cmd/namespace.go index e3a886b484..9f38938e63 100644 --- a/cmd/pgo/cmd/namespace.go +++ b/cmd/pgo/cmd/namespace.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/pgadmin.go b/cmd/pgo/cmd/pgadmin.go index bd629e0f7e..7e22612c03 100644 --- a/cmd/pgo/cmd/pgadmin.go +++ b/cmd/pgo/cmd/pgadmin.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/pgbouncer.go b/cmd/pgo/cmd/pgbouncer.go index ba2ec6f803..7053b390d6 100644 --- a/cmd/pgo/cmd/pgbouncer.go +++ b/cmd/pgo/cmd/pgbouncer.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/pgdump.go b/cmd/pgo/cmd/pgdump.go index d10e31b43d..26342fcb83 100644 --- a/cmd/pgo/cmd/pgdump.go +++ b/cmd/pgo/cmd/pgdump.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/pgorole.go b/cmd/pgo/cmd/pgorole.go index 110eca9887..45b08d7623 100644 --- a/cmd/pgo/cmd/pgorole.go +++ b/cmd/pgo/cmd/pgorole.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/pgouser.go b/cmd/pgo/cmd/pgouser.go index fa74050ffe..191467f3c1 100644 --- a/cmd/pgo/cmd/pgouser.go +++ b/cmd/pgo/cmd/pgouser.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/policy.go b/cmd/pgo/cmd/policy.go index a503910bb9..ef43016893 100644 --- a/cmd/pgo/cmd/policy.go +++ b/cmd/pgo/cmd/policy.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/pvc.go b/cmd/pgo/cmd/pvc.go index 9f910f0555..d9952a6e4a 100644 --- a/cmd/pgo/cmd/pvc.go +++ b/cmd/pgo/cmd/pvc.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/reload.go b/cmd/pgo/cmd/reload.go index bb0098ee86..e811185f1f 100644 --- a/cmd/pgo/cmd/reload.go +++ b/cmd/pgo/cmd/reload.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/restart.go b/cmd/pgo/cmd/restart.go index 650bcd16fd..7ea29d8405 100644 --- a/cmd/pgo/cmd/restart.go +++ b/cmd/pgo/cmd/restart.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/restore.go b/cmd/pgo/cmd/restore.go index 309808ee8d..692e19fea8 100644 --- a/cmd/pgo/cmd/restore.go +++ b/cmd/pgo/cmd/restore.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/root.go b/cmd/pgo/cmd/root.go index c38ba543c6..e09994ce6a 100644 --- a/cmd/pgo/cmd/root.go +++ b/cmd/pgo/cmd/root.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/scale.go b/cmd/pgo/cmd/scale.go index 833b7de3af..e92e0c605b 100644 --- a/cmd/pgo/cmd/scale.go +++ b/cmd/pgo/cmd/scale.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/scaledown.go b/cmd/pgo/cmd/scaledown.go index ba526c3e74..47f4b7e902 100644 --- a/cmd/pgo/cmd/scaledown.go +++ b/cmd/pgo/cmd/scaledown.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/schedule.go b/cmd/pgo/cmd/schedule.go index a4d66d7f69..20717ff229 100644 --- a/cmd/pgo/cmd/schedule.go +++ b/cmd/pgo/cmd/schedule.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/show.go b/cmd/pgo/cmd/show.go index 6c75afa100..c23024761d 100644 --- a/cmd/pgo/cmd/show.go +++ b/cmd/pgo/cmd/show.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/status.go b/cmd/pgo/cmd/status.go index fd13a7f140..75e3ff019f 100644 --- a/cmd/pgo/cmd/status.go +++ b/cmd/pgo/cmd/status.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/test.go b/cmd/pgo/cmd/test.go index c183879247..bcaf62c316 100644 --- a/cmd/pgo/cmd/test.go +++ b/cmd/pgo/cmd/test.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/update.go b/cmd/pgo/cmd/update.go index 87df8d5150..5225a10809 100644 --- a/cmd/pgo/cmd/update.go +++ b/cmd/pgo/cmd/update.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/upgrade.go b/cmd/pgo/cmd/upgrade.go index 2781210f4b..0571f813a6 100644 --- a/cmd/pgo/cmd/upgrade.go +++ b/cmd/pgo/cmd/upgrade.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/user.go b/cmd/pgo/cmd/user.go index 59d6cb1142..d42c7004c8 100644 --- a/cmd/pgo/cmd/user.go +++ b/cmd/pgo/cmd/user.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/version.go b/cmd/pgo/cmd/version.go index cf269d06b3..39482eb64f 100644 --- a/cmd/pgo/cmd/version.go +++ b/cmd/pgo/cmd/version.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/watch.go b/cmd/pgo/cmd/watch.go index 79746296a7..43f7f52eb4 100644 --- a/cmd/pgo/cmd/watch.go +++ b/cmd/pgo/cmd/watch.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/workflow.go b/cmd/pgo/cmd/workflow.go index fd372fd254..309df61b5b 100644 --- a/cmd/pgo/cmd/workflow.go +++ b/cmd/pgo/cmd/workflow.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/generatedocs.go b/cmd/pgo/generatedocs.go index 8bd31cfb01..2131ccfc72 100644 --- a/cmd/pgo/generatedocs.go +++ b/cmd/pgo/generatedocs.go @@ -3,7 +3,7 @@ package main /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/main.go b/cmd/pgo/main.go index f122259ee9..8c525bd72a 100644 --- a/cmd/pgo/main.go +++ b/cmd/pgo/main.go @@ -1,7 +1,7 @@ package main /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/util/confirmation.go b/cmd/pgo/util/confirmation.go index 4d15e0ae83..732f4de70a 100644 --- a/cmd/pgo/util/confirmation.go +++ b/cmd/pgo/util/confirmation.go @@ -1,7 +1,7 @@ package util /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/util/pad.go b/cmd/pgo/util/pad.go index fee08615ed..8c4affbbdf 100644 --- a/cmd/pgo/util/pad.go +++ b/cmd/pgo/util/pad.go @@ -1,7 +1,7 @@ package util /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/util/validation.go b/cmd/pgo/util/validation.go index 4e011ef43f..4b169a4d24 100644 --- a/cmd/pgo/util/validation.go +++ b/cmd/pgo/util/validation.go @@ -1,7 +1,7 @@ package util /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/postgres-operator/main.go b/cmd/postgres-operator/main.go index 052b11377f..f103d1b235 100644 --- a/cmd/postgres-operator/main.go +++ b/cmd/postgres-operator/main.go @@ -1,7 +1,7 @@ package main /* -Copyright 2017 - 2021 Crunchy Data +Copyright 2017 - 2022 Crunchy Data 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 diff --git a/cmd/postgres-operator/open_telemetry.go b/cmd/postgres-operator/open_telemetry.go index 3e1f51da24..44b3880e1f 100644 --- a/cmd/postgres-operator/open_telemetry.go +++ b/cmd/postgres-operator/open_telemetry.go @@ -1,7 +1,7 @@ package main /* -Copyright 2020 - 2021 Crunchy Data +Copyright 2020 - 2022 Crunchy Data 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 diff --git a/deploy/add-targeted-namespace-reconcile-rbac.sh b/deploy/add-targeted-namespace-reconcile-rbac.sh index 533e8507f9..66eddb96a0 100755 --- a/deploy/add-targeted-namespace-reconcile-rbac.sh +++ b/deploy/add-targeted-namespace-reconcile-rbac.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2020 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/add-targeted-namespace.sh b/deploy/add-targeted-namespace.sh index 61647e9feb..9d9b0e58d2 100755 --- a/deploy/add-targeted-namespace.sh +++ b/deploy/add-targeted-namespace.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/cleannamespaces.sh b/deploy/cleannamespaces.sh index 169bae6853..3ad11e8766 100755 --- a/deploy/cleannamespaces.sh +++ b/deploy/cleannamespaces.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/cleanup-rbac.sh b/deploy/cleanup-rbac.sh index df60c14500..61e07e13b4 100755 --- a/deploy/cleanup-rbac.sh +++ b/deploy/cleanup-rbac.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/cleanup.sh b/deploy/cleanup.sh index 711e276823..0068cb0fd7 100755 --- a/deploy/cleanup.sh +++ b/deploy/cleanup.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/deploy.sh b/deploy/deploy.sh index 34cb7332a4..e2abc5c25c 100755 --- a/deploy/deploy.sh +++ b/deploy/deploy.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/gen-api-keys.sh b/deploy/gen-api-keys.sh index 3d59b8aa99..4c985908c2 100755 --- a/deploy/gen-api-keys.sh +++ b/deploy/gen-api-keys.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/install-bootstrap-creds.sh b/deploy/install-bootstrap-creds.sh index 6eb2ff13b2..28c249b42a 100755 --- a/deploy/install-bootstrap-creds.sh +++ b/deploy/install-bootstrap-creds.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/install-rbac.sh b/deploy/install-rbac.sh index ec7a4d7d49..9c1b900ca7 100755 --- a/deploy/install-rbac.sh +++ b/deploy/install-rbac.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/remove-crd.sh b/deploy/remove-crd.sh index f14bbfd022..3d2182c739 100755 --- a/deploy/remove-crd.sh +++ b/deploy/remove-crd.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/setupnamespaces.sh b/deploy/setupnamespaces.sh index 08aade3518..50f5e874a6 100755 --- a/deploy/setupnamespaces.sh +++ b/deploy/setupnamespaces.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/show-crd.sh b/deploy/show-crd.sh index 091c2b6810..6af0001e7c 100755 --- a/deploy/show-crd.sh +++ b/deploy/show-crd.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/upgrade-creds.sh b/deploy/upgrade-creds.sh index dfea9c10c1..1b1024202a 100755 --- a/deploy/upgrade-creds.sh +++ b/deploy/upgrade-creds.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/upgrade-pgo.sh b/deploy/upgrade-pgo.sh index 87496f3c49..9ddbfd5910 100755 --- a/deploy/upgrade-pgo.sh +++ b/deploy/upgrade-pgo.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2020 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/docs/layouts/partials/flex/body-aftercontent.html b/docs/layouts/partials/flex/body-aftercontent.html index 8d5f330f56..1ec038ea12 100644 --- a/docs/layouts/partials/flex/body-aftercontent.html +++ b/docs/layouts/partials/flex/body-aftercontent.html @@ -35,7 +35,7 @@
-

© 2017 - 2021 Crunchy Data Solutions, Inc.

+

© 2017 - 2022 Crunchy Data Solutions, Inc.

diff --git a/examples/create-by-resource/run.sh b/examples/create-by-resource/run.sh index afb5e896e5..1f174d844c 100755 --- a/examples/create-by-resource/run.sh +++ b/examples/create-by-resource/run.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/examples/custom-config/create.sh b/examples/custom-config/create.sh index 5900132568..519ab0433e 100755 --- a/examples/custom-config/create.sh +++ b/examples/custom-config/create.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/examples/custom-config/setup.sql b/examples/custom-config/setup.sql index 1a05bce487..ca7510ff93 100644 --- a/examples/custom-config/setup.sql +++ b/examples/custom-config/setup.sql @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + * Copyright 2017 - 2022 Crunchy Data Solutions, Inc. * 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 diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt index e681957476..22523257ce 100644 --- a/hack/boilerplate.go.txt +++ b/hack/boilerplate.go.txt @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/hack/config_sync.sh b/hack/config_sync.sh index 6317c556a1..b7a7b95b95 100755 --- a/hack/config_sync.sh +++ b/hack/config_sync.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2020 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index 43e607336a..e53bdb9eda 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2020 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/installers/ansible/roles/pgo-operator/templates/add-targeted-namespace.sh.j2 b/installers/ansible/roles/pgo-operator/templates/add-targeted-namespace.sh.j2 index ec5e9e82d6..b8f2ab2e9f 100644 --- a/installers/ansible/roles/pgo-operator/templates/add-targeted-namespace.sh.j2 +++ b/installers/ansible/roles/pgo-operator/templates/add-targeted-namespace.sh.j2 @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/installers/image/bin/pgo-deploy.sh b/installers/image/bin/pgo-deploy.sh index 92fc955e9a..088d37c5de 100755 --- a/installers/image/bin/pgo-deploy.sh +++ b/installers/image/bin/pgo-deploy.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2020 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/installers/kubectl/client-setup.sh b/installers/kubectl/client-setup.sh index 1eaadc8c14..9106c7db9d 100755 --- a/installers/kubectl/client-setup.sh +++ b/installers/kubectl/client-setup.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2020 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/internal/apiserver/backrestservice/backrestimpl.go b/internal/apiserver/backrestservice/backrestimpl.go index 812b14f95c..d3344334a8 100644 --- a/internal/apiserver/backrestservice/backrestimpl.go +++ b/internal/apiserver/backrestservice/backrestimpl.go @@ -1,7 +1,7 @@ package backrestservice /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/backrestservice/backrestservice.go b/internal/apiserver/backrestservice/backrestservice.go index d2f4810872..18b5aea9db 100644 --- a/internal/apiserver/backrestservice/backrestservice.go +++ b/internal/apiserver/backrestservice/backrestservice.go @@ -1,7 +1,7 @@ package backrestservice /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/backupoptions/backupoptionsutil.go b/internal/apiserver/backupoptions/backupoptionsutil.go index 908aa1edb9..ec4bac560c 100644 --- a/internal/apiserver/backupoptions/backupoptionsutil.go +++ b/internal/apiserver/backupoptions/backupoptionsutil.go @@ -1,7 +1,7 @@ package backupoptions /* -Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/backupoptions/backupoptionsutil_test.go b/internal/apiserver/backupoptions/backupoptionsutil_test.go index 44048f15d8..8a996b4591 100644 --- a/internal/apiserver/backupoptions/backupoptionsutil_test.go +++ b/internal/apiserver/backupoptions/backupoptionsutil_test.go @@ -1,7 +1,7 @@ package backupoptions /* -Copyright 2021 Crunchy Data Solutions, Inc. +Copyright 2021 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/backupoptions/pgbackrestoptions.go b/internal/apiserver/backupoptions/pgbackrestoptions.go index e4c9e6347a..0a05f2d74c 100644 --- a/internal/apiserver/backupoptions/pgbackrestoptions.go +++ b/internal/apiserver/backupoptions/pgbackrestoptions.go @@ -1,7 +1,7 @@ package backupoptions /* -Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/backupoptions/pgdumpoptions.go b/internal/apiserver/backupoptions/pgdumpoptions.go index cd4c218be6..405fc1926c 100644 --- a/internal/apiserver/backupoptions/pgdumpoptions.go +++ b/internal/apiserver/backupoptions/pgdumpoptions.go @@ -1,7 +1,7 @@ package backupoptions /* -Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/catservice/catimpl.go b/internal/apiserver/catservice/catimpl.go index f1ed27bf73..2664da6edb 100644 --- a/internal/apiserver/catservice/catimpl.go +++ b/internal/apiserver/catservice/catimpl.go @@ -1,7 +1,7 @@ package catservice /* -Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/catservice/catservice.go b/internal/apiserver/catservice/catservice.go index 5084eb3a33..1c3ea605d0 100644 --- a/internal/apiserver/catservice/catservice.go +++ b/internal/apiserver/catservice/catservice.go @@ -1,7 +1,7 @@ package catservice /* -Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index 13f89419c1..52eb4cc248 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -1,7 +1,7 @@ package clusterservice /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/clusterservice/clusterservice.go b/internal/apiserver/clusterservice/clusterservice.go index 197a7c1315..2d8ece09fe 100644 --- a/internal/apiserver/clusterservice/clusterservice.go +++ b/internal/apiserver/clusterservice/clusterservice.go @@ -1,7 +1,7 @@ package clusterservice /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/clusterservice/scaleimpl.go b/internal/apiserver/clusterservice/scaleimpl.go index 6115d538f3..9688d58bcd 100644 --- a/internal/apiserver/clusterservice/scaleimpl.go +++ b/internal/apiserver/clusterservice/scaleimpl.go @@ -1,7 +1,7 @@ package clusterservice /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/clusterservice/scaleservice.go b/internal/apiserver/clusterservice/scaleservice.go index 113f730f24..8e8aad311a 100644 --- a/internal/apiserver/clusterservice/scaleservice.go +++ b/internal/apiserver/clusterservice/scaleservice.go @@ -1,7 +1,7 @@ package clusterservice /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/common.go b/internal/apiserver/common.go index d58201b276..3f6154b937 100644 --- a/internal/apiserver/common.go +++ b/internal/apiserver/common.go @@ -1,7 +1,7 @@ package apiserver /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/common_test.go b/internal/apiserver/common_test.go index 7b31071603..c0f788fede 100644 --- a/internal/apiserver/common_test.go +++ b/internal/apiserver/common_test.go @@ -1,7 +1,7 @@ package apiserver /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/configservice/configimpl.go b/internal/apiserver/configservice/configimpl.go index dc7ec2274c..6f956479c0 100644 --- a/internal/apiserver/configservice/configimpl.go +++ b/internal/apiserver/configservice/configimpl.go @@ -1,7 +1,7 @@ package configservice /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/configservice/configservice.go b/internal/apiserver/configservice/configservice.go index fddabe30b8..370d514873 100644 --- a/internal/apiserver/configservice/configservice.go +++ b/internal/apiserver/configservice/configservice.go @@ -1,7 +1,7 @@ package configservice /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/dfservice/dfimpl.go b/internal/apiserver/dfservice/dfimpl.go index 3014821621..f2b7525d66 100644 --- a/internal/apiserver/dfservice/dfimpl.go +++ b/internal/apiserver/dfservice/dfimpl.go @@ -1,7 +1,7 @@ package dfservice /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/dfservice/dfservice.go b/internal/apiserver/dfservice/dfservice.go index 8f42c65eef..4770d78b49 100644 --- a/internal/apiserver/dfservice/dfservice.go +++ b/internal/apiserver/dfservice/dfservice.go @@ -1,7 +1,7 @@ package dfservice /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/failoverservice/failoverimpl.go b/internal/apiserver/failoverservice/failoverimpl.go index 7d75420d44..643d8c9436 100644 --- a/internal/apiserver/failoverservice/failoverimpl.go +++ b/internal/apiserver/failoverservice/failoverimpl.go @@ -1,7 +1,7 @@ package failoverservice /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/failoverservice/failoverservice.go b/internal/apiserver/failoverservice/failoverservice.go index 08461e9356..64eb1476cf 100644 --- a/internal/apiserver/failoverservice/failoverservice.go +++ b/internal/apiserver/failoverservice/failoverservice.go @@ -1,7 +1,7 @@ package failoverservice /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/labelservice/labelimpl.go b/internal/apiserver/labelservice/labelimpl.go index ffe975311b..c053aab613 100644 --- a/internal/apiserver/labelservice/labelimpl.go +++ b/internal/apiserver/labelservice/labelimpl.go @@ -1,7 +1,7 @@ package labelservice /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/labelservice/labelservice.go b/internal/apiserver/labelservice/labelservice.go index de5e199ebe..38bfb16a62 100644 --- a/internal/apiserver/labelservice/labelservice.go +++ b/internal/apiserver/labelservice/labelservice.go @@ -1,7 +1,7 @@ package labelservice /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/middleware.go b/internal/apiserver/middleware.go index 58a1fcd77d..3977341a34 100644 --- a/internal/apiserver/middleware.go +++ b/internal/apiserver/middleware.go @@ -1,7 +1,7 @@ package apiserver /* -Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/namespaceservice/namespaceimpl.go b/internal/apiserver/namespaceservice/namespaceimpl.go index c51d6b6f11..7106fc3a0b 100644 --- a/internal/apiserver/namespaceservice/namespaceimpl.go +++ b/internal/apiserver/namespaceservice/namespaceimpl.go @@ -1,7 +1,7 @@ package namespaceservice /* -Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/namespaceservice/namespaceservice.go b/internal/apiserver/namespaceservice/namespaceservice.go index 2416e27731..38c3835699 100644 --- a/internal/apiserver/namespaceservice/namespaceservice.go +++ b/internal/apiserver/namespaceservice/namespaceservice.go @@ -1,7 +1,7 @@ package namespaceservice /* -Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/perms.go b/internal/apiserver/perms.go index 01906db43e..9e692dc27f 100644 --- a/internal/apiserver/perms.go +++ b/internal/apiserver/perms.go @@ -1,7 +1,7 @@ package apiserver /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgadminservice/pgadminimpl.go b/internal/apiserver/pgadminservice/pgadminimpl.go index a0154b37f9..2416c86556 100644 --- a/internal/apiserver/pgadminservice/pgadminimpl.go +++ b/internal/apiserver/pgadminservice/pgadminimpl.go @@ -1,7 +1,7 @@ package pgadminservice /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgadminservice/pgadminservice.go b/internal/apiserver/pgadminservice/pgadminservice.go index fc24b5b94c..23fdcb2404 100644 --- a/internal/apiserver/pgadminservice/pgadminservice.go +++ b/internal/apiserver/pgadminservice/pgadminservice.go @@ -1,7 +1,7 @@ package pgadminservice /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgbouncerservice/pgbouncerimpl.go b/internal/apiserver/pgbouncerservice/pgbouncerimpl.go index 872b922ca6..14c2f8f093 100644 --- a/internal/apiserver/pgbouncerservice/pgbouncerimpl.go +++ b/internal/apiserver/pgbouncerservice/pgbouncerimpl.go @@ -1,7 +1,7 @@ package pgbouncerservice /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgbouncerservice/pgbouncerservice.go b/internal/apiserver/pgbouncerservice/pgbouncerservice.go index 978f21bac2..707a356e32 100644 --- a/internal/apiserver/pgbouncerservice/pgbouncerservice.go +++ b/internal/apiserver/pgbouncerservice/pgbouncerservice.go @@ -1,7 +1,7 @@ package pgbouncerservice /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgdumpservice/pgdumpimpl.go b/internal/apiserver/pgdumpservice/pgdumpimpl.go index 52c90d9006..a2d5b2457e 100644 --- a/internal/apiserver/pgdumpservice/pgdumpimpl.go +++ b/internal/apiserver/pgdumpservice/pgdumpimpl.go @@ -1,7 +1,7 @@ package pgdumpservice /* -Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgdumpservice/pgdumpservice.go b/internal/apiserver/pgdumpservice/pgdumpservice.go index 28acf0933a..b3a3976a9a 100644 --- a/internal/apiserver/pgdumpservice/pgdumpservice.go +++ b/internal/apiserver/pgdumpservice/pgdumpservice.go @@ -1,7 +1,7 @@ package pgdumpservice /* -Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgoroleservice/pgoroleimpl.go b/internal/apiserver/pgoroleservice/pgoroleimpl.go index 4f10d76f3e..6d945ab709 100644 --- a/internal/apiserver/pgoroleservice/pgoroleimpl.go +++ b/internal/apiserver/pgoroleservice/pgoroleimpl.go @@ -1,7 +1,7 @@ package pgoroleservice /* -Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgoroleservice/pgoroleservice.go b/internal/apiserver/pgoroleservice/pgoroleservice.go index 581927de8f..c53b9d56b4 100644 --- a/internal/apiserver/pgoroleservice/pgoroleservice.go +++ b/internal/apiserver/pgoroleservice/pgoroleservice.go @@ -1,7 +1,7 @@ package pgoroleservice /* -Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgouserservice/pgouserimpl.go b/internal/apiserver/pgouserservice/pgouserimpl.go index 415f8d1b95..e45376c403 100644 --- a/internal/apiserver/pgouserservice/pgouserimpl.go +++ b/internal/apiserver/pgouserservice/pgouserimpl.go @@ -1,7 +1,7 @@ package pgouserservice /* -Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgouserservice/pgouserservice.go b/internal/apiserver/pgouserservice/pgouserservice.go index 5ccff33a2e..a28a9e871d 100644 --- a/internal/apiserver/pgouserservice/pgouserservice.go +++ b/internal/apiserver/pgouserservice/pgouserservice.go @@ -1,7 +1,7 @@ package pgouserservice /* -Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/policyservice/policyimpl.go b/internal/apiserver/policyservice/policyimpl.go index e06da11b83..be49ef72f7 100644 --- a/internal/apiserver/policyservice/policyimpl.go +++ b/internal/apiserver/policyservice/policyimpl.go @@ -1,7 +1,7 @@ package policyservice /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/policyservice/policyservice.go b/internal/apiserver/policyservice/policyservice.go index 290b8271a0..750bff651e 100644 --- a/internal/apiserver/policyservice/policyservice.go +++ b/internal/apiserver/policyservice/policyservice.go @@ -1,7 +1,7 @@ package policyservice /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pvcservice/pvcimpl.go b/internal/apiserver/pvcservice/pvcimpl.go index 5f22e1abba..81f2058139 100644 --- a/internal/apiserver/pvcservice/pvcimpl.go +++ b/internal/apiserver/pvcservice/pvcimpl.go @@ -1,7 +1,7 @@ package pvcservice /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pvcservice/pvcservice.go b/internal/apiserver/pvcservice/pvcservice.go index f3a994d078..bf8b6b0440 100644 --- a/internal/apiserver/pvcservice/pvcservice.go +++ b/internal/apiserver/pvcservice/pvcservice.go @@ -1,7 +1,7 @@ package pvcservice /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/reloadservice/reloadimpl.go b/internal/apiserver/reloadservice/reloadimpl.go index 465b6a9e26..e6856e7221 100644 --- a/internal/apiserver/reloadservice/reloadimpl.go +++ b/internal/apiserver/reloadservice/reloadimpl.go @@ -1,7 +1,7 @@ package reloadservice /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/reloadservice/reloadservice.go b/internal/apiserver/reloadservice/reloadservice.go index 14e8463f5e..b58dccf8c1 100644 --- a/internal/apiserver/reloadservice/reloadservice.go +++ b/internal/apiserver/reloadservice/reloadservice.go @@ -1,7 +1,7 @@ package reloadservice /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/restartservice/restartimpl.go b/internal/apiserver/restartservice/restartimpl.go index 4defee23b3..dc8b7e6853 100644 --- a/internal/apiserver/restartservice/restartimpl.go +++ b/internal/apiserver/restartservice/restartimpl.go @@ -1,7 +1,7 @@ package restartservice /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/restartservice/restartservice.go b/internal/apiserver/restartservice/restartservice.go index f8d24ac4e2..3dae8ca3ad 100644 --- a/internal/apiserver/restartservice/restartservice.go +++ b/internal/apiserver/restartservice/restartservice.go @@ -1,7 +1,7 @@ package restartservice /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/root.go b/internal/apiserver/root.go index e8afecbd65..ea5c29b6a1 100644 --- a/internal/apiserver/root.go +++ b/internal/apiserver/root.go @@ -1,7 +1,7 @@ package apiserver /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/routing/doc.go b/internal/apiserver/routing/doc.go index 2807dfb978..c24eebfbe6 100644 --- a/internal/apiserver/routing/doc.go +++ b/internal/apiserver/routing/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/routing/routes.go b/internal/apiserver/routing/routes.go index 495682052a..e90c8ea43b 100644 --- a/internal/apiserver/routing/routes.go +++ b/internal/apiserver/routing/routes.go @@ -1,7 +1,7 @@ package routing /* -Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/scheduleservice/scheduleimpl.go b/internal/apiserver/scheduleservice/scheduleimpl.go index 07efb121a8..70384c87cf 100644 --- a/internal/apiserver/scheduleservice/scheduleimpl.go +++ b/internal/apiserver/scheduleservice/scheduleimpl.go @@ -1,7 +1,7 @@ package scheduleservice /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/scheduleservice/scheduleservice.go b/internal/apiserver/scheduleservice/scheduleservice.go index 4508c30e7a..8f5ecdb26a 100644 --- a/internal/apiserver/scheduleservice/scheduleservice.go +++ b/internal/apiserver/scheduleservice/scheduleservice.go @@ -1,7 +1,7 @@ package scheduleservice /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/statusservice/statusimpl.go b/internal/apiserver/statusservice/statusimpl.go index 4c1f72b1a1..c7bf254c78 100644 --- a/internal/apiserver/statusservice/statusimpl.go +++ b/internal/apiserver/statusservice/statusimpl.go @@ -1,7 +1,7 @@ package statusservice /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/statusservice/statusservice.go b/internal/apiserver/statusservice/statusservice.go index 5618f821e4..01de6c5687 100644 --- a/internal/apiserver/statusservice/statusservice.go +++ b/internal/apiserver/statusservice/statusservice.go @@ -1,7 +1,7 @@ package statusservice /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/upgradeservice/upgradeimpl.go b/internal/apiserver/upgradeservice/upgradeimpl.go index 05d67e932f..2c5e911bdf 100644 --- a/internal/apiserver/upgradeservice/upgradeimpl.go +++ b/internal/apiserver/upgradeservice/upgradeimpl.go @@ -1,7 +1,7 @@ package upgradeservice /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/upgradeservice/upgradeservice.go b/internal/apiserver/upgradeservice/upgradeservice.go index aa4b5ebb2e..0e8eb1d785 100644 --- a/internal/apiserver/upgradeservice/upgradeservice.go +++ b/internal/apiserver/upgradeservice/upgradeservice.go @@ -1,7 +1,7 @@ package upgradeservice /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/userservice/userimpl.go b/internal/apiserver/userservice/userimpl.go index 9f7998d6ed..ba57e86017 100644 --- a/internal/apiserver/userservice/userimpl.go +++ b/internal/apiserver/userservice/userimpl.go @@ -1,7 +1,7 @@ package userservice /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/userservice/userimpl_test.go b/internal/apiserver/userservice/userimpl_test.go index 2a458d3589..37e6fb6f0b 100644 --- a/internal/apiserver/userservice/userimpl_test.go +++ b/internal/apiserver/userservice/userimpl_test.go @@ -1,7 +1,7 @@ package userservice /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/userservice/userservice.go b/internal/apiserver/userservice/userservice.go index 2dda7305e4..01a862483f 100644 --- a/internal/apiserver/userservice/userservice.go +++ b/internal/apiserver/userservice/userservice.go @@ -1,7 +1,7 @@ package userservice /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/versionservice/versionimpl.go b/internal/apiserver/versionservice/versionimpl.go index 959cfae669..fb62a4a9bb 100644 --- a/internal/apiserver/versionservice/versionimpl.go +++ b/internal/apiserver/versionservice/versionimpl.go @@ -1,7 +1,7 @@ package versionservice /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/versionservice/versionservice.go b/internal/apiserver/versionservice/versionservice.go index 6fd72719b5..80cf3833cb 100644 --- a/internal/apiserver/versionservice/versionservice.go +++ b/internal/apiserver/versionservice/versionservice.go @@ -1,7 +1,7 @@ package versionservice /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/workflowservice/workflowimpl.go b/internal/apiserver/workflowservice/workflowimpl.go index debf2a31c2..c4edc6592d 100644 --- a/internal/apiserver/workflowservice/workflowimpl.go +++ b/internal/apiserver/workflowservice/workflowimpl.go @@ -1,7 +1,7 @@ package workflowservice /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/workflowservice/workflowservice.go b/internal/apiserver/workflowservice/workflowservice.go index 89a2e29796..ef498e84a8 100644 --- a/internal/apiserver/workflowservice/workflowservice.go +++ b/internal/apiserver/workflowservice/workflowservice.go @@ -1,7 +1,7 @@ package workflowservice /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/config/annotations.go b/internal/config/annotations.go index f8a0b32023..51bd1f1ec5 100644 --- a/internal/config/annotations.go +++ b/internal/config/annotations.go @@ -1,7 +1,7 @@ package config /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/config/defaults.go b/internal/config/defaults.go index 776dfd7c90..a63340f16d 100644 --- a/internal/config/defaults.go +++ b/internal/config/defaults.go @@ -1,7 +1,7 @@ package config /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/config/images.go b/internal/config/images.go index 0deb5b4cd6..019e0f400e 100644 --- a/internal/config/images.go +++ b/internal/config/images.go @@ -1,7 +1,7 @@ package config /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/config/labels.go b/internal/config/labels.go index f7c55b79ea..4105406e83 100644 --- a/internal/config/labels.go +++ b/internal/config/labels.go @@ -1,7 +1,7 @@ package config /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/config/pgoconfig.go b/internal/config/pgoconfig.go index 5150168ba4..ce9cb966d6 100644 --- a/internal/config/pgoconfig.go +++ b/internal/config/pgoconfig.go @@ -1,7 +1,7 @@ package config /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/config/secrets.go b/internal/config/secrets.go index 769ec70781..b33e9d2f18 100644 --- a/internal/config/secrets.go +++ b/internal/config/secrets.go @@ -1,7 +1,7 @@ package config /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/config/volumes.go b/internal/config/volumes.go index f49c8d916d..c90d56bbb3 100644 --- a/internal/config/volumes.go +++ b/internal/config/volumes.go @@ -1,7 +1,7 @@ package config /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/configmap/configmapcontroller.go b/internal/controller/configmap/configmapcontroller.go index 7c9cb27d2b..d37d586b6e 100644 --- a/internal/controller/configmap/configmapcontroller.go +++ b/internal/controller/configmap/configmapcontroller.go @@ -1,7 +1,7 @@ package configmap /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/configmap/synchandler.go b/internal/controller/configmap/synchandler.go index ac673b1746..249e7d21b3 100644 --- a/internal/controller/configmap/synchandler.go +++ b/internal/controller/configmap/synchandler.go @@ -1,7 +1,7 @@ package configmap /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/controllerutil.go b/internal/controller/controllerutil.go index f196d84be9..59591133de 100644 --- a/internal/controller/controllerutil.go +++ b/internal/controller/controllerutil.go @@ -1,7 +1,7 @@ package controller /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/job/backresthandler.go b/internal/controller/job/backresthandler.go index 85330244c1..a64525bbdb 100644 --- a/internal/controller/job/backresthandler.go +++ b/internal/controller/job/backresthandler.go @@ -1,7 +1,7 @@ package job /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/job/bootstraphandler.go b/internal/controller/job/bootstraphandler.go index 6ba9cd9b19..a54f76d3be 100644 --- a/internal/controller/job/bootstraphandler.go +++ b/internal/controller/job/bootstraphandler.go @@ -1,7 +1,7 @@ package job /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/job/jobcontroller.go b/internal/controller/job/jobcontroller.go index 3db9406f56..b78330ef9d 100644 --- a/internal/controller/job/jobcontroller.go +++ b/internal/controller/job/jobcontroller.go @@ -1,7 +1,7 @@ package job /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/job/jobevents.go b/internal/controller/job/jobevents.go index 7d45c23006..1e930f2b07 100644 --- a/internal/controller/job/jobevents.go +++ b/internal/controller/job/jobevents.go @@ -1,7 +1,7 @@ package job /* -Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/job/jobutil.go b/internal/controller/job/jobutil.go index e7a3113469..76135bcc32 100644 --- a/internal/controller/job/jobutil.go +++ b/internal/controller/job/jobutil.go @@ -1,7 +1,7 @@ package job /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/job/pgdumphandler.go b/internal/controller/job/pgdumphandler.go index b407ec262a..934e49e8a0 100644 --- a/internal/controller/job/pgdumphandler.go +++ b/internal/controller/job/pgdumphandler.go @@ -1,7 +1,7 @@ package job /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/job/rmdatahandler.go b/internal/controller/job/rmdatahandler.go index 73ce88a486..0fce9efe07 100644 --- a/internal/controller/job/rmdatahandler.go +++ b/internal/controller/job/rmdatahandler.go @@ -1,7 +1,7 @@ package job /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/manager/controllermanager.go b/internal/controller/manager/controllermanager.go index afb678d21a..7670ac10ee 100644 --- a/internal/controller/manager/controllermanager.go +++ b/internal/controller/manager/controllermanager.go @@ -1,7 +1,7 @@ package manager /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/manager/rbac.go b/internal/controller/manager/rbac.go index 4068d67b86..ea0d845f9c 100644 --- a/internal/controller/manager/rbac.go +++ b/internal/controller/manager/rbac.go @@ -1,7 +1,7 @@ package manager /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/namespace/namespacecontroller.go b/internal/controller/namespace/namespacecontroller.go index 0c651cfbdf..4fb53032f3 100644 --- a/internal/controller/namespace/namespacecontroller.go +++ b/internal/controller/namespace/namespacecontroller.go @@ -1,7 +1,7 @@ package namespace /* -Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/pgcluster/pgclustercontroller.go b/internal/controller/pgcluster/pgclustercontroller.go index e53ff48350..25e03b33bc 100644 --- a/internal/controller/pgcluster/pgclustercontroller.go +++ b/internal/controller/pgcluster/pgclustercontroller.go @@ -1,7 +1,7 @@ package pgcluster /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/pgpolicy/pgpolicycontroller.go b/internal/controller/pgpolicy/pgpolicycontroller.go index 5a1f6a2fc5..52ce680af1 100644 --- a/internal/controller/pgpolicy/pgpolicycontroller.go +++ b/internal/controller/pgpolicy/pgpolicycontroller.go @@ -1,7 +1,7 @@ package pgpolicy /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/pgreplica/pgreplicacontroller.go b/internal/controller/pgreplica/pgreplicacontroller.go index 5d0d8c01a8..d6af6485b2 100644 --- a/internal/controller/pgreplica/pgreplicacontroller.go +++ b/internal/controller/pgreplica/pgreplicacontroller.go @@ -1,7 +1,7 @@ package pgreplica /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/pgtask/backresthandler.go b/internal/controller/pgtask/backresthandler.go index 6cbcbff8f8..d650164b02 100644 --- a/internal/controller/pgtask/backresthandler.go +++ b/internal/controller/pgtask/backresthandler.go @@ -1,7 +1,7 @@ package pgtask /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/pgtask/pgtaskcontroller.go b/internal/controller/pgtask/pgtaskcontroller.go index d52ccf6dd0..bf2ed904b3 100644 --- a/internal/controller/pgtask/pgtaskcontroller.go +++ b/internal/controller/pgtask/pgtaskcontroller.go @@ -1,7 +1,7 @@ package pgtask /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/pod/inithandler.go b/internal/controller/pod/inithandler.go index 444159f231..49304df7fe 100644 --- a/internal/controller/pod/inithandler.go +++ b/internal/controller/pod/inithandler.go @@ -1,7 +1,7 @@ package pod /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/pod/podcontroller.go b/internal/controller/pod/podcontroller.go index cbfe3ba2db..bf339e7bb5 100644 --- a/internal/controller/pod/podcontroller.go +++ b/internal/controller/pod/podcontroller.go @@ -1,7 +1,7 @@ package pod /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/pod/podevents.go b/internal/controller/pod/podevents.go index d019c2b4b4..b87fa41fe9 100644 --- a/internal/controller/pod/podevents.go +++ b/internal/controller/pod/podevents.go @@ -1,7 +1,7 @@ package pod /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/pod/promotionhandler.go b/internal/controller/pod/promotionhandler.go index e1a35e25c8..a8ad3ba261 100644 --- a/internal/controller/pod/promotionhandler.go +++ b/internal/controller/pod/promotionhandler.go @@ -1,7 +1,7 @@ package pod /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/client_config.go b/internal/kubeapi/client_config.go index c9e0b5ea5c..7bea0453ee 100644 --- a/internal/kubeapi/client_config.go +++ b/internal/kubeapi/client_config.go @@ -1,7 +1,7 @@ package kubeapi /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/endpoints.go b/internal/kubeapi/endpoints.go index e871dbf5d6..df9d157aa6 100644 --- a/internal/kubeapi/endpoints.go +++ b/internal/kubeapi/endpoints.go @@ -1,7 +1,7 @@ package kubeapi /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/errors.go b/internal/kubeapi/errors.go index ab5e9c07aa..b3f79e73ca 100644 --- a/internal/kubeapi/errors.go +++ b/internal/kubeapi/errors.go @@ -1,7 +1,7 @@ package kubeapi /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/exec.go b/internal/kubeapi/exec.go index c154ba1fa3..d807980c31 100644 --- a/internal/kubeapi/exec.go +++ b/internal/kubeapi/exec.go @@ -1,7 +1,7 @@ package kubeapi /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/fake/clientset.go b/internal/kubeapi/fake/clientset.go index 2ac060e1da..7623fea9c3 100644 --- a/internal/kubeapi/fake/clientset.go +++ b/internal/kubeapi/fake/clientset.go @@ -1,7 +1,7 @@ package fake /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/fake/fakeclients.go b/internal/kubeapi/fake/fakeclients.go index cdc96c1ec6..f45865b942 100644 --- a/internal/kubeapi/fake/fakeclients.go +++ b/internal/kubeapi/fake/fakeclients.go @@ -1,7 +1,7 @@ package fake /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/patch.go b/internal/kubeapi/patch.go index 57f11e6867..2042127db1 100644 --- a/internal/kubeapi/patch.go +++ b/internal/kubeapi/patch.go @@ -1,7 +1,7 @@ package kubeapi /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/patch_test.go b/internal/kubeapi/patch_test.go index 706ee4768d..a751f5f04b 100644 --- a/internal/kubeapi/patch_test.go +++ b/internal/kubeapi/patch_test.go @@ -1,7 +1,7 @@ package kubeapi /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/volumes.go b/internal/kubeapi/volumes.go index 795b0a9151..26e1b2dd63 100644 --- a/internal/kubeapi/volumes.go +++ b/internal/kubeapi/volumes.go @@ -1,7 +1,7 @@ package kubeapi /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/volumes_test.go b/internal/kubeapi/volumes_test.go index fead7d17be..921f454dad 100644 --- a/internal/kubeapi/volumes_test.go +++ b/internal/kubeapi/volumes_test.go @@ -1,7 +1,7 @@ package kubeapi /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/logging/loglib.go b/internal/logging/loglib.go index d171ba29fd..e36e5ba199 100644 --- a/internal/logging/loglib.go +++ b/internal/logging/loglib.go @@ -2,7 +2,7 @@ package logging /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/ns/nslogic.go b/internal/ns/nslogic.go index c9c53dd480..58acc7c849 100644 --- a/internal/ns/nslogic.go +++ b/internal/ns/nslogic.go @@ -1,7 +1,7 @@ package ns /* -Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/backrest/backup.go b/internal/operator/backrest/backup.go index cd4f31ed05..b37e19836a 100644 --- a/internal/operator/backrest/backup.go +++ b/internal/operator/backrest/backup.go @@ -1,7 +1,7 @@ package backrest /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/backrest/repo.go b/internal/operator/backrest/repo.go index 0195a8debf..7c883540cf 100644 --- a/internal/operator/backrest/repo.go +++ b/internal/operator/backrest/repo.go @@ -1,7 +1,7 @@ package backrest /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/backrest/restore.go b/internal/operator/backrest/restore.go index da631f8758..2523d4c60c 100644 --- a/internal/operator/backrest/restore.go +++ b/internal/operator/backrest/restore.go @@ -1,7 +1,7 @@ package backrest /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/backrest/stanza.go b/internal/operator/backrest/stanza.go index e9f1fc6c50..6d7166da33 100644 --- a/internal/operator/backrest/stanza.go +++ b/internal/operator/backrest/stanza.go @@ -1,7 +1,7 @@ package backrest /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/cluster.go b/internal/operator/cluster/cluster.go index d07938e8e7..9acb5025f2 100644 --- a/internal/operator/cluster/cluster.go +++ b/internal/operator/cluster/cluster.go @@ -4,7 +4,7 @@ package cluster /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/clusterlogic.go b/internal/operator/cluster/clusterlogic.go index c839527684..8a5ac33e3c 100644 --- a/internal/operator/cluster/clusterlogic.go +++ b/internal/operator/cluster/clusterlogic.go @@ -4,7 +4,7 @@ package cluster /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/common.go b/internal/operator/cluster/common.go index 1974462b92..d3ee882510 100644 --- a/internal/operator/cluster/common.go +++ b/internal/operator/cluster/common.go @@ -1,7 +1,7 @@ package cluster /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/common_test.go b/internal/operator/cluster/common_test.go index b2666b4d91..e662a7a3d6 100644 --- a/internal/operator/cluster/common_test.go +++ b/internal/operator/cluster/common_test.go @@ -1,7 +1,7 @@ package cluster /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/exporter.go b/internal/operator/cluster/exporter.go index 929f06b85f..b14b81df5f 100644 --- a/internal/operator/cluster/exporter.go +++ b/internal/operator/cluster/exporter.go @@ -1,7 +1,7 @@ package cluster /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/pgadmin.go b/internal/operator/cluster/pgadmin.go index 9f716ea9e1..fd3d9a4fe2 100644 --- a/internal/operator/cluster/pgadmin.go +++ b/internal/operator/cluster/pgadmin.go @@ -1,7 +1,7 @@ package cluster /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/pgbadger.go b/internal/operator/cluster/pgbadger.go index b24c40ebf5..3e67a93bf3 100644 --- a/internal/operator/cluster/pgbadger.go +++ b/internal/operator/cluster/pgbadger.go @@ -1,7 +1,7 @@ package cluster /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/pgbouncer.go b/internal/operator/cluster/pgbouncer.go index e65fdcf143..6c7004d908 100644 --- a/internal/operator/cluster/pgbouncer.go +++ b/internal/operator/cluster/pgbouncer.go @@ -1,7 +1,7 @@ package cluster /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/pgbouncer_test.go b/internal/operator/cluster/pgbouncer_test.go index 2c07739e43..a789f3bb2d 100644 --- a/internal/operator/cluster/pgbouncer_test.go +++ b/internal/operator/cluster/pgbouncer_test.go @@ -1,7 +1,7 @@ package cluster /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/rolling.go b/internal/operator/cluster/rolling.go index 0811fb3d99..5dc55c9e92 100644 --- a/internal/operator/cluster/rolling.go +++ b/internal/operator/cluster/rolling.go @@ -1,7 +1,7 @@ package cluster /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/service.go b/internal/operator/cluster/service.go index c651551d3d..d0aa6f7487 100644 --- a/internal/operator/cluster/service.go +++ b/internal/operator/cluster/service.go @@ -4,7 +4,7 @@ package cluster /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/standby.go b/internal/operator/cluster/standby.go index 696ac1ad18..9c645c24da 100644 --- a/internal/operator/cluster/standby.go +++ b/internal/operator/cluster/standby.go @@ -1,7 +1,7 @@ package cluster /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/upgrade.go b/internal/operator/cluster/upgrade.go index 5fa3de7002..3fb2d39e8c 100644 --- a/internal/operator/cluster/upgrade.go +++ b/internal/operator/cluster/upgrade.go @@ -1,7 +1,7 @@ package cluster /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/clusterutilities.go b/internal/operator/clusterutilities.go index 239bcbe8f3..0a2fdaed69 100644 --- a/internal/operator/clusterutilities.go +++ b/internal/operator/clusterutilities.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/clusterutilities_test.go b/internal/operator/clusterutilities_test.go index 80824ddd6e..d858d3245a 100644 --- a/internal/operator/clusterutilities_test.go +++ b/internal/operator/clusterutilities_test.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/common.go b/internal/operator/common.go index be71596b63..ba1eb47e21 100644 --- a/internal/operator/common.go +++ b/internal/operator/common.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/common_test.go b/internal/operator/common_test.go index 88bb7f633b..6217b66fab 100644 --- a/internal/operator/common_test.go +++ b/internal/operator/common_test.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/config/configutil.go b/internal/operator/config/configutil.go index 5d8648b098..bd20522cf7 100644 --- a/internal/operator/config/configutil.go +++ b/internal/operator/config/configutil.go @@ -1,7 +1,7 @@ package config /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/config/dcs.go b/internal/operator/config/dcs.go index 270330c4cd..53fdcbd3e3 100644 --- a/internal/operator/config/dcs.go +++ b/internal/operator/config/dcs.go @@ -1,7 +1,7 @@ package config /* - Copyright 2020 - 2021 Crunchy Data Solutions, Ind. + Copyright 2020 - 2022 Crunchy Data Solutions, Ind. 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 diff --git a/internal/operator/config/localdb.go b/internal/operator/config/localdb.go index eab42dd6bf..dc4c759f6b 100644 --- a/internal/operator/config/localdb.go +++ b/internal/operator/config/localdb.go @@ -1,7 +1,7 @@ package config /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inl. + Copyright 2020 - 2022 Crunchy Data Solutions, Inl. 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 diff --git a/internal/operator/failover.go b/internal/operator/failover.go index 1334a09989..ba3f841a6d 100644 --- a/internal/operator/failover.go +++ b/internal/operator/failover.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/failover_test.go b/internal/operator/failover_test.go index 67f3be31ef..9b41035fef 100644 --- a/internal/operator/failover_test.go +++ b/internal/operator/failover_test.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2021 Crunchy Data Solutions, Inc. + Copyright 2021 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/operatorupgrade/version-check.go b/internal/operator/operatorupgrade/version-check.go index a63773e9f2..ea5327b951 100644 --- a/internal/operator/operatorupgrade/version-check.go +++ b/internal/operator/operatorupgrade/version-check.go @@ -1,7 +1,7 @@ package operatorupgrade /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/pgbackrest.go b/internal/operator/pgbackrest.go index 19c255da2c..81b440c921 100644 --- a/internal/operator/pgbackrest.go +++ b/internal/operator/pgbackrest.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/pgbackrest_test.go b/internal/operator/pgbackrest_test.go index 38d09a6be6..f853e0e363 100644 --- a/internal/operator/pgbackrest_test.go +++ b/internal/operator/pgbackrest_test.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/pgdump/dump.go b/internal/operator/pgdump/dump.go index 3a19a32ac3..fb8b437122 100644 --- a/internal/operator/pgdump/dump.go +++ b/internal/operator/pgdump/dump.go @@ -1,7 +1,7 @@ package pgdump /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/pgdump/restore.go b/internal/operator/pgdump/restore.go index 0603f179d7..a09ef6a098 100644 --- a/internal/operator/pgdump/restore.go +++ b/internal/operator/pgdump/restore.go @@ -1,7 +1,7 @@ package pgdump /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/pvc/pvc.go b/internal/operator/pvc/pvc.go index 5e96d67c8e..069ba34a25 100644 --- a/internal/operator/pvc/pvc.go +++ b/internal/operator/pvc/pvc.go @@ -1,7 +1,7 @@ package pvc /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/storage.go b/internal/operator/storage.go index b6c06b1abd..ba7f512a9a 100644 --- a/internal/operator/storage.go +++ b/internal/operator/storage.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/storage_test.go b/internal/operator/storage_test.go index 46b9161dbe..5c817c1780 100644 --- a/internal/operator/storage_test.go +++ b/internal/operator/storage_test.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/switchover.go b/internal/operator/switchover.go index 6595f61208..56a21fb7dc 100644 --- a/internal/operator/switchover.go +++ b/internal/operator/switchover.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2021 Crunchy Data Solutions, Inc. + Copyright 2021 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/switchover_test.go b/internal/operator/switchover_test.go index 9d9abba2ba..e11cf08061 100644 --- a/internal/operator/switchover_test.go +++ b/internal/operator/switchover_test.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2021 Crunchy Data Solutions, Inc. + Copyright 2021 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/task/applypolicies.go b/internal/operator/task/applypolicies.go index 823800451e..5eabbcad3c 100644 --- a/internal/operator/task/applypolicies.go +++ b/internal/operator/task/applypolicies.go @@ -1,7 +1,7 @@ package task /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/task/rmdata.go b/internal/operator/task/rmdata.go index 5341a66156..fbf0087dd3 100644 --- a/internal/operator/task/rmdata.go +++ b/internal/operator/task/rmdata.go @@ -1,7 +1,7 @@ package task /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/task/workflow.go b/internal/operator/task/workflow.go index a890859293..d6920f3c95 100644 --- a/internal/operator/task/workflow.go +++ b/internal/operator/task/workflow.go @@ -1,7 +1,7 @@ package task /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/wal.go b/internal/operator/wal.go index b9a14c3219..ca0889ca50 100644 --- a/internal/operator/wal.go +++ b/internal/operator/wal.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/patroni/doc.go b/internal/patroni/doc.go index 6311d1e653..62c153097e 100644 --- a/internal/patroni/doc.go +++ b/internal/patroni/doc.go @@ -4,7 +4,7 @@ package patroni /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/patroni/patroni.go b/internal/patroni/patroni.go index 70651dcace..e22aaf49f4 100644 --- a/internal/patroni/patroni.go +++ b/internal/patroni/patroni.go @@ -1,7 +1,7 @@ package patroni /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/pgadmin/backoff.go b/internal/pgadmin/backoff.go index ee62223e82..94c2b1b2db 100644 --- a/internal/pgadmin/backoff.go +++ b/internal/pgadmin/backoff.go @@ -1,7 +1,7 @@ package pgadmin /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/pgadmin/backoff_test.go b/internal/pgadmin/backoff_test.go index ec4195df4c..5a04a69c6e 100644 --- a/internal/pgadmin/backoff_test.go +++ b/internal/pgadmin/backoff_test.go @@ -1,7 +1,7 @@ package pgadmin /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/pgadmin/crypto.go b/internal/pgadmin/crypto.go index e2db5beb5d..6b116a6b0d 100644 --- a/internal/pgadmin/crypto.go +++ b/internal/pgadmin/crypto.go @@ -1,7 +1,7 @@ package pgadmin /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/pgadmin/crypto_test.go b/internal/pgadmin/crypto_test.go index aeb18a1fcb..bf301f2a2e 100644 --- a/internal/pgadmin/crypto_test.go +++ b/internal/pgadmin/crypto_test.go @@ -1,7 +1,7 @@ package pgadmin /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/pgadmin/doc.go b/internal/pgadmin/doc.go index 58bf983ab5..d02d6feb3c 100644 --- a/internal/pgadmin/doc.go +++ b/internal/pgadmin/doc.go @@ -4,7 +4,7 @@ database which powers pgadmin */ package pgadmin /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/pgadmin/hash.go b/internal/pgadmin/hash.go index beaab646c9..cc2f2d2e5f 100644 --- a/internal/pgadmin/hash.go +++ b/internal/pgadmin/hash.go @@ -1,7 +1,7 @@ package pgadmin /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/pgadmin/logic.go b/internal/pgadmin/logic.go index 9bd6cda94a..da8a56bcc4 100644 --- a/internal/pgadmin/logic.go +++ b/internal/pgadmin/logic.go @@ -1,7 +1,7 @@ package pgadmin /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/pgadmin/runner.go b/internal/pgadmin/runner.go index ab052b431c..f4f948728e 100644 --- a/internal/pgadmin/runner.go +++ b/internal/pgadmin/runner.go @@ -1,7 +1,7 @@ package pgadmin /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/pgadmin/server.go b/internal/pgadmin/server.go index b5ab3b6ef5..dcc68dd26c 100644 --- a/internal/pgadmin/server.go +++ b/internal/pgadmin/server.go @@ -1,7 +1,7 @@ package pgadmin /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/postgres/doc.go b/internal/postgres/doc.go index 2a2155a2cd..8010931373 100644 --- a/internal/postgres/doc.go +++ b/internal/postgres/doc.go @@ -5,7 +5,7 @@ package postgres /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/postgres/password/doc.go b/internal/postgres/password/doc.go index b0e22372f6..8950878ee2 100644 --- a/internal/postgres/password/doc.go +++ b/internal/postgres/password/doc.go @@ -4,7 +4,7 @@ package password /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/postgres/password/md5.go b/internal/postgres/password/md5.go index 1697e04cb9..537b07fdfd 100644 --- a/internal/postgres/password/md5.go +++ b/internal/postgres/password/md5.go @@ -1,7 +1,7 @@ package password /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/postgres/password/md5_test.go b/internal/postgres/password/md5_test.go index 7adabe8831..9646ae283b 100644 --- a/internal/postgres/password/md5_test.go +++ b/internal/postgres/password/md5_test.go @@ -1,7 +1,7 @@ package password /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/postgres/password/password.go b/internal/postgres/password/password.go index 923c854f00..072570f1cd 100644 --- a/internal/postgres/password/password.go +++ b/internal/postgres/password/password.go @@ -1,7 +1,7 @@ package password /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/postgres/password/password_test.go b/internal/postgres/password/password_test.go index 40962b9a75..7bd2afee9e 100644 --- a/internal/postgres/password/password_test.go +++ b/internal/postgres/password/password_test.go @@ -1,7 +1,7 @@ package password /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/postgres/password/scram.go b/internal/postgres/password/scram.go index 1411af9636..62f4bf2649 100644 --- a/internal/postgres/password/scram.go +++ b/internal/postgres/password/scram.go @@ -1,7 +1,7 @@ package password /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/postgres/password/scram_test.go b/internal/postgres/password/scram_test.go index 4995191655..d20cfa0f40 100644 --- a/internal/postgres/password/scram_test.go +++ b/internal/postgres/password/scram_test.go @@ -1,7 +1,7 @@ package password /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/tlsutil/primitives.go b/internal/tlsutil/primitives.go index 363630fec1..7c56f9ece1 100644 --- a/internal/tlsutil/primitives.go +++ b/internal/tlsutil/primitives.go @@ -1,7 +1,7 @@ package tlsutil /* -Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/tlsutil/primitives_test.go b/internal/tlsutil/primitives_test.go index 684e4e3df6..31df75d46b 100644 --- a/internal/tlsutil/primitives_test.go +++ b/internal/tlsutil/primitives_test.go @@ -1,7 +1,7 @@ package tlsutil /* -Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/backrest.go b/internal/util/backrest.go index 50235ce0ad..d8121b38a6 100644 --- a/internal/util/backrest.go +++ b/internal/util/backrest.go @@ -1,7 +1,7 @@ package util /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/cluster.go b/internal/util/cluster.go index 03328b23af..0b35dbc9b6 100644 --- a/internal/util/cluster.go +++ b/internal/util/cluster.go @@ -1,7 +1,7 @@ package util /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/cluster_test.go b/internal/util/cluster_test.go index 98d3a0a1f0..3df855e78c 100644 --- a/internal/util/cluster_test.go +++ b/internal/util/cluster_test.go @@ -1,7 +1,7 @@ package util /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/exporter.go b/internal/util/exporter.go index 6b4423b13a..b9b39d47a2 100644 --- a/internal/util/exporter.go +++ b/internal/util/exporter.go @@ -1,7 +1,7 @@ package util /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/exporter_test.go b/internal/util/exporter_test.go index b614c4272d..9ea7fe0be2 100644 --- a/internal/util/exporter_test.go +++ b/internal/util/exporter_test.go @@ -1,7 +1,7 @@ package util /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/failover.go b/internal/util/failover.go index 7c530d93b1..fe743753df 100644 --- a/internal/util/failover.go +++ b/internal/util/failover.go @@ -1,7 +1,7 @@ package util /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/pgbouncer.go b/internal/util/pgbouncer.go index ff1033fada..7771dffa2a 100644 --- a/internal/util/pgbouncer.go +++ b/internal/util/pgbouncer.go @@ -1,7 +1,7 @@ package util /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/policy.go b/internal/util/policy.go index 567623e21b..467cf124f3 100644 --- a/internal/util/policy.go +++ b/internal/util/policy.go @@ -1,7 +1,7 @@ package util /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/secrets.go b/internal/util/secrets.go index 6a692ce377..046f3df73a 100644 --- a/internal/util/secrets.go +++ b/internal/util/secrets.go @@ -1,7 +1,7 @@ package util /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/secrets_test.go b/internal/util/secrets_test.go index 8dca7649bb..2754541d20 100644 --- a/internal/util/secrets_test.go +++ b/internal/util/secrets_test.go @@ -1,7 +1,7 @@ package util /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/ssh.go b/internal/util/ssh.go index 17c4904753..e835bbb470 100644 --- a/internal/util/ssh.go +++ b/internal/util/ssh.go @@ -1,7 +1,7 @@ package util /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/util.go b/internal/util/util.go index 532568e783..b3bbaa74fb 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -1,7 +1,7 @@ package util /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/cluster.go b/pkg/apis/crunchydata.com/v1/cluster.go index b8b88b4e1d..d1244f779d 100644 --- a/pkg/apis/crunchydata.com/v1/cluster.go +++ b/pkg/apis/crunchydata.com/v1/cluster.go @@ -1,7 +1,7 @@ package v1 /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/cluster_test.go b/pkg/apis/crunchydata.com/v1/cluster_test.go index 8a31175cf4..f26938594d 100644 --- a/pkg/apis/crunchydata.com/v1/cluster_test.go +++ b/pkg/apis/crunchydata.com/v1/cluster_test.go @@ -1,7 +1,7 @@ package v1 /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/common.go b/pkg/apis/crunchydata.com/v1/common.go index c768a0d408..46ed360ecd 100644 --- a/pkg/apis/crunchydata.com/v1/common.go +++ b/pkg/apis/crunchydata.com/v1/common.go @@ -1,7 +1,7 @@ package v1 /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/common_test.go b/pkg/apis/crunchydata.com/v1/common_test.go index cde6832420..853be90ba1 100644 --- a/pkg/apis/crunchydata.com/v1/common_test.go +++ b/pkg/apis/crunchydata.com/v1/common_test.go @@ -1,7 +1,7 @@ package v1 /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/doc.go b/pkg/apis/crunchydata.com/v1/doc.go index 46fdea5004..57d2cf8d79 100644 --- a/pkg/apis/crunchydata.com/v1/doc.go +++ b/pkg/apis/crunchydata.com/v1/doc.go @@ -108,7 +108,7 @@ package v1 // +k8s:deepcopy-gen=package,register /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/errors.go b/pkg/apis/crunchydata.com/v1/errors.go index 9a1fbc30b1..f1ee079ce8 100644 --- a/pkg/apis/crunchydata.com/v1/errors.go +++ b/pkg/apis/crunchydata.com/v1/errors.go @@ -3,7 +3,7 @@ package v1 import "errors" /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/policy.go b/pkg/apis/crunchydata.com/v1/policy.go index df08940188..07b669f49e 100644 --- a/pkg/apis/crunchydata.com/v1/policy.go +++ b/pkg/apis/crunchydata.com/v1/policy.go @@ -1,7 +1,7 @@ package v1 /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/register.go b/pkg/apis/crunchydata.com/v1/register.go index 586424126d..53bc23aeac 100644 --- a/pkg/apis/crunchydata.com/v1/register.go +++ b/pkg/apis/crunchydata.com/v1/register.go @@ -1,7 +1,7 @@ package v1 /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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/pkg/apis/crunchydata.com/v1/replica.go b/pkg/apis/crunchydata.com/v1/replica.go index 878ff63481..f4100305c4 100644 --- a/pkg/apis/crunchydata.com/v1/replica.go +++ b/pkg/apis/crunchydata.com/v1/replica.go @@ -1,7 +1,7 @@ package v1 /* - Copyright 2018 - 2021 Crunchy Data Solutions, Inc. + Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/task.go b/pkg/apis/crunchydata.com/v1/task.go index 58340cc900..6d4a5a9657 100644 --- a/pkg/apis/crunchydata.com/v1/task.go +++ b/pkg/apis/crunchydata.com/v1/task.go @@ -1,7 +1,7 @@ package v1 /* - Copyright 2017 - 2021 Crunchy Data Solutions, Inc. + Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go b/pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go index 6534215bbf..708a49f64f 100644 --- a/pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go +++ b/pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/backrestmsgs.go b/pkg/apiservermsgs/backrestmsgs.go index 5a11e963bc..887fed79a9 100644 --- a/pkg/apiservermsgs/backrestmsgs.go +++ b/pkg/apiservermsgs/backrestmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/catmsgs.go b/pkg/apiservermsgs/catmsgs.go index 15f7d5cf85..b1eb5dd69f 100644 --- a/pkg/apiservermsgs/catmsgs.go +++ b/pkg/apiservermsgs/catmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/clustermsgs.go b/pkg/apiservermsgs/clustermsgs.go index 726e511622..e3d4ae83e9 100644 --- a/pkg/apiservermsgs/clustermsgs.go +++ b/pkg/apiservermsgs/clustermsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/common.go b/pkg/apiservermsgs/common.go index d24bf9de7f..f72690b593 100644 --- a/pkg/apiservermsgs/common.go +++ b/pkg/apiservermsgs/common.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/configmsgs.go b/pkg/apiservermsgs/configmsgs.go index 1a4f8aae9c..a7f0631401 100644 --- a/pkg/apiservermsgs/configmsgs.go +++ b/pkg/apiservermsgs/configmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/dfmsgs.go b/pkg/apiservermsgs/dfmsgs.go index 8d947768ef..7e7e980eac 100644 --- a/pkg/apiservermsgs/dfmsgs.go +++ b/pkg/apiservermsgs/dfmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/failovermsgs.go b/pkg/apiservermsgs/failovermsgs.go index 208c7bc2ee..30040c016e 100644 --- a/pkg/apiservermsgs/failovermsgs.go +++ b/pkg/apiservermsgs/failovermsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/labelmsgs.go b/pkg/apiservermsgs/labelmsgs.go index c28430c818..44019b3ace 100644 --- a/pkg/apiservermsgs/labelmsgs.go +++ b/pkg/apiservermsgs/labelmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/namespacemsgs.go b/pkg/apiservermsgs/namespacemsgs.go index 5fc4665a9b..dda8a3fe3a 100644 --- a/pkg/apiservermsgs/namespacemsgs.go +++ b/pkg/apiservermsgs/namespacemsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/pgadminmsgs.go b/pkg/apiservermsgs/pgadminmsgs.go index 73e4475294..9241a9cc43 100644 --- a/pkg/apiservermsgs/pgadminmsgs.go +++ b/pkg/apiservermsgs/pgadminmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/pgbouncermsgs.go b/pkg/apiservermsgs/pgbouncermsgs.go index 9dd37ffb14..73083f0c84 100644 --- a/pkg/apiservermsgs/pgbouncermsgs.go +++ b/pkg/apiservermsgs/pgbouncermsgs.go @@ -3,7 +3,7 @@ package apiservermsgs import v1 "k8s.io/api/core/v1" /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/pgdumpmsgs.go b/pkg/apiservermsgs/pgdumpmsgs.go index c83269648a..58753f66cc 100644 --- a/pkg/apiservermsgs/pgdumpmsgs.go +++ b/pkg/apiservermsgs/pgdumpmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/pgorolemsgs.go b/pkg/apiservermsgs/pgorolemsgs.go index 6aae3494d0..1705452624 100644 --- a/pkg/apiservermsgs/pgorolemsgs.go +++ b/pkg/apiservermsgs/pgorolemsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/pgousermsgs.go b/pkg/apiservermsgs/pgousermsgs.go index 4690c1f888..e636c71b61 100644 --- a/pkg/apiservermsgs/pgousermsgs.go +++ b/pkg/apiservermsgs/pgousermsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/policymsgs.go b/pkg/apiservermsgs/policymsgs.go index 023676cf6b..b733c5373e 100644 --- a/pkg/apiservermsgs/policymsgs.go +++ b/pkg/apiservermsgs/policymsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/pvcmsgs.go b/pkg/apiservermsgs/pvcmsgs.go index da902da96b..7c4f1a366c 100644 --- a/pkg/apiservermsgs/pvcmsgs.go +++ b/pkg/apiservermsgs/pvcmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/reloadmsgs.go b/pkg/apiservermsgs/reloadmsgs.go index 11c4293980..ae23783d05 100644 --- a/pkg/apiservermsgs/reloadmsgs.go +++ b/pkg/apiservermsgs/reloadmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/restartmsgs.go b/pkg/apiservermsgs/restartmsgs.go index e9bf5b8b57..495c4ae7b1 100644 --- a/pkg/apiservermsgs/restartmsgs.go +++ b/pkg/apiservermsgs/restartmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/schedulemsgs.go b/pkg/apiservermsgs/schedulemsgs.go index 47e9f90ca8..529d60ddb6 100644 --- a/pkg/apiservermsgs/schedulemsgs.go +++ b/pkg/apiservermsgs/schedulemsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/statusmsgs.go b/pkg/apiservermsgs/statusmsgs.go index 72a6c79aab..923cd7fe77 100644 --- a/pkg/apiservermsgs/statusmsgs.go +++ b/pkg/apiservermsgs/statusmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/upgrademsgs.go b/pkg/apiservermsgs/upgrademsgs.go index a360c036c7..7e30d254de 100644 --- a/pkg/apiservermsgs/upgrademsgs.go +++ b/pkg/apiservermsgs/upgrademsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/usermsgs.go b/pkg/apiservermsgs/usermsgs.go index 276f35152f..eabbe3e3b4 100644 --- a/pkg/apiservermsgs/usermsgs.go +++ b/pkg/apiservermsgs/usermsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/versionmsgs.go b/pkg/apiservermsgs/versionmsgs.go index 38ab640cdb..91618ec76a 100644 --- a/pkg/apiservermsgs/versionmsgs.go +++ b/pkg/apiservermsgs/versionmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +Copyright 2017 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/watchmsgs.go b/pkg/apiservermsgs/watchmsgs.go index 02c1472b90..4d6c48e65c 100644 --- a/pkg/apiservermsgs/watchmsgs.go +++ b/pkg/apiservermsgs/watchmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2019 - 2021 Crunchy Data Solutions, Inc. +Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/workflowmsgs.go b/pkg/apiservermsgs/workflowmsgs.go index 3d4c44353a..4c4be0bd45 100644 --- a/pkg/apiservermsgs/workflowmsgs.go +++ b/pkg/apiservermsgs/workflowmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +Copyright 2018 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/events/eventing.go b/pkg/events/eventing.go index d3b28793af..e69355b1b2 100644 --- a/pkg/events/eventing.go +++ b/pkg/events/eventing.go @@ -1,7 +1,7 @@ package events /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/events/eventtype.go b/pkg/events/eventtype.go index 9781277883..e538b8d030 100644 --- a/pkg/events/eventtype.go +++ b/pkg/events/eventtype.go @@ -1,7 +1,7 @@ package events /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/events/pgoeventtype.go b/pkg/events/pgoeventtype.go index 4e4f114868..305c9197f0 100644 --- a/pkg/events/pgoeventtype.go +++ b/pkg/events/pgoeventtype.go @@ -1,7 +1,7 @@ package events /* - Copyright 2019 - 2021 Crunchy Data Solutions, Inc. + Copyright 2019 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/clientset.go b/pkg/generated/clientset/versioned/clientset.go index 7a20991345..73eb41a8a4 100644 --- a/pkg/generated/clientset/versioned/clientset.go +++ b/pkg/generated/clientset/versioned/clientset.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/doc.go b/pkg/generated/clientset/versioned/doc.go index f862afa1b0..c8faf9c342 100644 --- a/pkg/generated/clientset/versioned/doc.go +++ b/pkg/generated/clientset/versioned/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/fake/clientset_generated.go b/pkg/generated/clientset/versioned/fake/clientset_generated.go index 5f9ec9bbbb..168269e377 100644 --- a/pkg/generated/clientset/versioned/fake/clientset_generated.go +++ b/pkg/generated/clientset/versioned/fake/clientset_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/fake/doc.go b/pkg/generated/clientset/versioned/fake/doc.go index e9300efbfe..79128b5478 100644 --- a/pkg/generated/clientset/versioned/fake/doc.go +++ b/pkg/generated/clientset/versioned/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/fake/register.go b/pkg/generated/clientset/versioned/fake/register.go index d33f1544e9..93abfdf223 100644 --- a/pkg/generated/clientset/versioned/fake/register.go +++ b/pkg/generated/clientset/versioned/fake/register.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/scheme/doc.go b/pkg/generated/clientset/versioned/scheme/doc.go index 49cfafd10d..264e52dac0 100644 --- a/pkg/generated/clientset/versioned/scheme/doc.go +++ b/pkg/generated/clientset/versioned/scheme/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/scheme/register.go b/pkg/generated/clientset/versioned/scheme/register.go index 2ce45ab80b..de17632bfe 100644 --- a/pkg/generated/clientset/versioned/scheme/register.go +++ b/pkg/generated/clientset/versioned/scheme/register.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/crunchydata.com_client.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/crunchydata.com_client.go index 4c862528fa..dfd0dd03c2 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/crunchydata.com_client.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/crunchydata.com_client.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/doc.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/doc.go index 21c249ea20..c7adf35aa6 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/doc.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/doc.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/doc.go index 14f506a6fb..f1a03c292f 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/doc.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_crunchydata.com_client.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_crunchydata.com_client.go index 33ad7a5550..6afd73a7ff 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_crunchydata.com_client.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_crunchydata.com_client.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgcluster.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgcluster.go index ff11262ddf..1a9a60e7ac 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgcluster.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgcluster.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgpolicy.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgpolicy.go index 5a661bb23a..3220e3239b 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgpolicy.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgpolicy.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgreplica.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgreplica.go index 05708f6b48..91a99e6248 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgreplica.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgreplica.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgtask.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgtask.go index df8a0479cd..190ba5701c 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgtask.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgtask.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/generated_expansion.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/generated_expansion.go index 5ea3a63db1..3f12094466 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/generated_expansion.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/generated_expansion.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgcluster.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgcluster.go index 45d3777e84..092525a65e 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgcluster.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgcluster.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgpolicy.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgpolicy.go index 8dbd0227c7..bce7004ef1 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgpolicy.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgpolicy.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgreplica.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgreplica.go index c9c553db30..7f945a2ca4 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgreplica.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgreplica.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgtask.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgtask.go index e1095c71e9..d596cf277e 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgtask.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgtask.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/informers/externalversions/crunchydata.com/interface.go b/pkg/generated/informers/externalversions/crunchydata.com/interface.go index 698763aff3..8aec2edcfb 100644 --- a/pkg/generated/informers/externalversions/crunchydata.com/interface.go +++ b/pkg/generated/informers/externalversions/crunchydata.com/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/informers/externalversions/crunchydata.com/v1/interface.go b/pkg/generated/informers/externalversions/crunchydata.com/v1/interface.go index b30e24b239..cbaf0ff49a 100644 --- a/pkg/generated/informers/externalversions/crunchydata.com/v1/interface.go +++ b/pkg/generated/informers/externalversions/crunchydata.com/v1/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/informers/externalversions/crunchydata.com/v1/pgcluster.go b/pkg/generated/informers/externalversions/crunchydata.com/v1/pgcluster.go index 1e9753596d..a43c757247 100644 --- a/pkg/generated/informers/externalversions/crunchydata.com/v1/pgcluster.go +++ b/pkg/generated/informers/externalversions/crunchydata.com/v1/pgcluster.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/informers/externalversions/crunchydata.com/v1/pgpolicy.go b/pkg/generated/informers/externalversions/crunchydata.com/v1/pgpolicy.go index 741ad3c39e..2439813877 100644 --- a/pkg/generated/informers/externalversions/crunchydata.com/v1/pgpolicy.go +++ b/pkg/generated/informers/externalversions/crunchydata.com/v1/pgpolicy.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/informers/externalversions/crunchydata.com/v1/pgreplica.go b/pkg/generated/informers/externalversions/crunchydata.com/v1/pgreplica.go index c7946ec142..913105590b 100644 --- a/pkg/generated/informers/externalversions/crunchydata.com/v1/pgreplica.go +++ b/pkg/generated/informers/externalversions/crunchydata.com/v1/pgreplica.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/informers/externalversions/crunchydata.com/v1/pgtask.go b/pkg/generated/informers/externalversions/crunchydata.com/v1/pgtask.go index 44398863fa..2c8c4b2744 100644 --- a/pkg/generated/informers/externalversions/crunchydata.com/v1/pgtask.go +++ b/pkg/generated/informers/externalversions/crunchydata.com/v1/pgtask.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/informers/externalversions/factory.go b/pkg/generated/informers/externalversions/factory.go index 65c18752b4..00e6004f0a 100644 --- a/pkg/generated/informers/externalversions/factory.go +++ b/pkg/generated/informers/externalversions/factory.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/informers/externalversions/generic.go b/pkg/generated/informers/externalversions/generic.go index 48e7491d80..7fa4a6e032 100644 --- a/pkg/generated/informers/externalversions/generic.go +++ b/pkg/generated/informers/externalversions/generic.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go b/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go index 130bc043a8..a7b137f9b6 100644 --- a/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go +++ b/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/listers/crunchydata.com/v1/expansion_generated.go b/pkg/generated/listers/crunchydata.com/v1/expansion_generated.go index 369c56b717..91a28bb8bd 100644 --- a/pkg/generated/listers/crunchydata.com/v1/expansion_generated.go +++ b/pkg/generated/listers/crunchydata.com/v1/expansion_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/listers/crunchydata.com/v1/pgcluster.go b/pkg/generated/listers/crunchydata.com/v1/pgcluster.go index 7bcf7f4328..410dc333de 100644 --- a/pkg/generated/listers/crunchydata.com/v1/pgcluster.go +++ b/pkg/generated/listers/crunchydata.com/v1/pgcluster.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/listers/crunchydata.com/v1/pgpolicy.go b/pkg/generated/listers/crunchydata.com/v1/pgpolicy.go index f4b39358a0..698dfee2f8 100644 --- a/pkg/generated/listers/crunchydata.com/v1/pgpolicy.go +++ b/pkg/generated/listers/crunchydata.com/v1/pgpolicy.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/listers/crunchydata.com/v1/pgreplica.go b/pkg/generated/listers/crunchydata.com/v1/pgreplica.go index f3bdf3bd68..9a8fa7f89a 100644 --- a/pkg/generated/listers/crunchydata.com/v1/pgreplica.go +++ b/pkg/generated/listers/crunchydata.com/v1/pgreplica.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/listers/crunchydata.com/v1/pgtask.go b/pkg/generated/listers/crunchydata.com/v1/pgtask.go index 1a46df7b78..d88a4ad9f3 100644 --- a/pkg/generated/listers/crunchydata.com/v1/pgtask.go +++ b/pkg/generated/listers/crunchydata.com/v1/pgtask.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 Crunchy Data Solutions, Inc. +Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/pv/create-pv-nfs-label.sh b/pv/create-pv-nfs-label.sh index a347a907fd..ec11b1b573 100755 --- a/pv/create-pv-nfs-label.sh +++ b/pv/create-pv-nfs-label.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2018 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/pv/create-pv-nfs-legacy.sh b/pv/create-pv-nfs-legacy.sh index 96d698e159..1f6a381035 100755 --- a/pv/create-pv-nfs-legacy.sh +++ b/pv/create-pv-nfs-legacy.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/pv/create-pv-nfs.sh b/pv/create-pv-nfs.sh index e1e71c95d8..60dd1f04d6 100755 --- a/pv/create-pv-nfs.sh +++ b/pv/create-pv-nfs.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/pv/create-pv.sh b/pv/create-pv.sh index 6d9ede0b71..44fb402031 100755 --- a/pv/create-pv.sh +++ b/pv/create-pv.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/pv/delete-pv.sh b/pv/delete-pv.sh index b3d7422ff2..3b1513184b 100755 --- a/pv/delete-pv.sh +++ b/pv/delete-pv.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 - 2021 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2022 Crunchy Data Solutions, Inc. # 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 diff --git a/testing/pgo_cli/cluster_annotation_test.go b/testing/pgo_cli/cluster_annotation_test.go index e0a2b9286f..66c94898f1 100644 --- a/testing/pgo_cli/cluster_annotation_test.go +++ b/testing/pgo_cli/cluster_annotation_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_backup_test.go b/testing/pgo_cli/cluster_backup_test.go index 527e8762ec..5b7d2e1740 100644 --- a/testing/pgo_cli/cluster_backup_test.go +++ b/testing/pgo_cli/cluster_backup_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_cat_test.go b/testing/pgo_cli/cluster_cat_test.go index aea958fb15..123648f5cf 100644 --- a/testing/pgo_cli/cluster_cat_test.go +++ b/testing/pgo_cli/cluster_cat_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_create_test.go b/testing/pgo_cli/cluster_create_test.go index f0579de8cd..228177a749 100644 --- a/testing/pgo_cli/cluster_create_test.go +++ b/testing/pgo_cli/cluster_create_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_delete_test.go b/testing/pgo_cli/cluster_delete_test.go index 1285b6026e..08cf13df79 100644 --- a/testing/pgo_cli/cluster_delete_test.go +++ b/testing/pgo_cli/cluster_delete_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_df_test.go b/testing/pgo_cli/cluster_df_test.go index 91ae0b8092..056c947d3c 100644 --- a/testing/pgo_cli/cluster_df_test.go +++ b/testing/pgo_cli/cluster_df_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_failover_test.go b/testing/pgo_cli/cluster_failover_test.go index e7b2f02585..f7dd9fd36a 100644 --- a/testing/pgo_cli/cluster_failover_test.go +++ b/testing/pgo_cli/cluster_failover_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_label_test.go b/testing/pgo_cli/cluster_label_test.go index ccf4a17461..67d9023604 100644 --- a/testing/pgo_cli/cluster_label_test.go +++ b/testing/pgo_cli/cluster_label_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_pgbouncer_test.go b/testing/pgo_cli/cluster_pgbouncer_test.go index b0f9199881..30aebd9571 100644 --- a/testing/pgo_cli/cluster_pgbouncer_test.go +++ b/testing/pgo_cli/cluster_pgbouncer_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_policy_test.go b/testing/pgo_cli/cluster_policy_test.go index 66db7c6080..eb08beee07 100644 --- a/testing/pgo_cli/cluster_policy_test.go +++ b/testing/pgo_cli/cluster_policy_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_pvc_test.go b/testing/pgo_cli/cluster_pvc_test.go index 0009225e4a..a55d962435 100644 --- a/testing/pgo_cli/cluster_pvc_test.go +++ b/testing/pgo_cli/cluster_pvc_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_reload_test.go b/testing/pgo_cli/cluster_reload_test.go index 02cf63a479..e0ffd7cc46 100644 --- a/testing/pgo_cli/cluster_reload_test.go +++ b/testing/pgo_cli/cluster_reload_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_restart_test.go b/testing/pgo_cli/cluster_restart_test.go index c46763731a..103ddf5b50 100644 --- a/testing/pgo_cli/cluster_restart_test.go +++ b/testing/pgo_cli/cluster_restart_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_scale_test.go b/testing/pgo_cli/cluster_scale_test.go index 219e44f582..475b54339d 100644 --- a/testing/pgo_cli/cluster_scale_test.go +++ b/testing/pgo_cli/cluster_scale_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_scaledown_test.go b/testing/pgo_cli/cluster_scaledown_test.go index 5e9dc16b28..e8fc1f1a46 100644 --- a/testing/pgo_cli/cluster_scaledown_test.go +++ b/testing/pgo_cli/cluster_scaledown_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_test_test.go b/testing/pgo_cli/cluster_test_test.go index 76100eb9f0..d1f40fe826 100644 --- a/testing/pgo_cli/cluster_test_test.go +++ b/testing/pgo_cli/cluster_test_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_user_test.go b/testing/pgo_cli/cluster_user_test.go index 964bb6b58c..17e416eab3 100644 --- a/testing/pgo_cli/cluster_user_test.go +++ b/testing/pgo_cli/cluster_user_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/operator_namespace_test.go b/testing/pgo_cli/operator_namespace_test.go index 57bc685ea2..32b8f5a85e 100644 --- a/testing/pgo_cli/operator_namespace_test.go +++ b/testing/pgo_cli/operator_namespace_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/operator_rbac_test.go b/testing/pgo_cli/operator_rbac_test.go index 569f1bd090..4a18548905 100644 --- a/testing/pgo_cli/operator_rbac_test.go +++ b/testing/pgo_cli/operator_rbac_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/operator_test.go b/testing/pgo_cli/operator_test.go index 09e8148c68..81442de7dc 100644 --- a/testing/pgo_cli/operator_test.go +++ b/testing/pgo_cli/operator_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/suite_helpers_test.go b/testing/pgo_cli/suite_helpers_test.go index b5f7489c16..8ee0cce55c 100644 --- a/testing/pgo_cli/suite_helpers_test.go +++ b/testing/pgo_cli/suite_helpers_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/suite_pgo_cmd_test.go b/testing/pgo_cli/suite_pgo_cmd_test.go index 245d314aa7..f19fb4a879 100644 --- a/testing/pgo_cli/suite_pgo_cmd_test.go +++ b/testing/pgo_cli/suite_pgo_cmd_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/suite_test.go b/testing/pgo_cli/suite_test.go index 9429f28278..bca4b81a5e 100644 --- a/testing/pgo_cli/suite_test.go +++ b/testing/pgo_cli/suite_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2021 Crunchy Data Solutions, Inc. + Copyright 2020 - 2022 Crunchy Data Solutions, Inc. 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 From 2c59aad531277ed20794fd71fb27cc4cf0b7aff4 Mon Sep 17 00:00:00 2001 From: Andrew L'Ecuyer Date: Tue, 22 Feb 2022 20:56:28 +0000 Subject: [PATCH 252/276] Version Bump for PG Updates & PGO v4.6.6 [sc-13826] --- Makefile | 4 ++-- README.md | 2 +- bin/push-ccp-to-gcr.sh | 2 +- conf/postgres-operator/pgo.yaml | 4 ++-- docs/config.toml | 13 ++++++------- docs/content/Configuration/compatibility.md | 5 +++++ .../advanced/crunchy-postgres-exporter.md | 2 +- examples/create-by-resource/fromcrd.json | 6 +++--- examples/envs.sh | 2 +- examples/helm/README.md | 4 ++-- examples/helm/postgres/Chart.yaml | 2 +- examples/helm/postgres/templates/pgcluster.yaml | 2 +- examples/helm/postgres/values.yaml | 2 +- examples/kustomize/createcluster/README.md | 16 ++++++++-------- .../kustomize/createcluster/base/pgcluster.yaml | 6 +++--- .../overlay/staging/hippo-rpl1-pgreplica.yaml | 2 +- installers/ansible/README.md | 2 +- installers/ansible/values.yaml | 6 +++--- installers/gcp-marketplace/Makefile | 2 +- installers/gcp-marketplace/README.md | 2 +- installers/gcp-marketplace/values.yaml | 6 +++--- installers/helm/Chart.yaml | 2 +- installers/helm/values.yaml | 6 +++--- installers/kubectl/client-setup.sh | 2 +- installers/kubectl/postgres-operator-ocp311.yml | 8 ++++---- installers/kubectl/postgres-operator.yml | 8 ++++---- installers/metrics/ansible/README.md | 2 +- installers/metrics/helm/Chart.yaml | 2 +- installers/metrics/helm/helm_template.yaml | 2 +- installers/metrics/helm/values.yaml | 2 +- .../kubectl/postgres-operator-metrics-ocp311.yml | 2 +- .../kubectl/postgres-operator-metrics.yml | 2 +- installers/olm/Makefile | 4 ++-- installers/olm/description.openshift.md | 2 +- installers/olm/description.upstream.md | 2 +- pkg/apis/crunchydata.com/v1/doc.go | 8 ++++---- pkg/apiservermsgs/common.go | 2 +- redhat/atomic/help.1 | 2 +- redhat/atomic/help.md | 2 +- 39 files changed, 78 insertions(+), 74 deletions(-) diff --git a/Makefile b/Makefile index 26270df410..a71f00dc47 100644 --- a/Makefile +++ b/Makefile @@ -5,9 +5,9 @@ PGOROOT ?= $(CURDIR) PGO_BASEOS ?= centos8 PGO_IMAGE_PREFIX ?= crunchydata PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) -PGO_VERSION ?= 4.6.5 +PGO_VERSION ?= 4.6.6 PGO_PG_VERSION ?= 13 -PGO_PG_FULLVERSION ?= 13.5 +PGO_PG_FULLVERSION ?= 13.6 PGO_BACKREST_VERSION ?= 2.31 PACKAGER ?= yum diff --git a/README.md b/README.md index 395852894c..20f25a3cd5 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ to start as quickly as: ```shell kubectl create namespace pgo -kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.5/installers/kubectl/postgres-operator.yml +kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.6/installers/kubectl/postgres-operator.yml ``` Otherwise, we highly recommend following the instructions from our [Quickstart](https://access.crunchydata.com/documentation/postgres-operator/latest/quickstart/). diff --git a/bin/push-ccp-to-gcr.sh b/bin/push-ccp-to-gcr.sh index 5780b8cd45..563311442c 100755 --- a/bin/push-ccp-to-gcr.sh +++ b/bin/push-ccp-to-gcr.sh @@ -16,7 +16,7 @@ GCR_IMAGE_PREFIX=gcr.io/crunchy-dev-test CCP_IMAGE_PREFIX=crunchydata -CCP_IMAGE_TAG=centos8-13.5-4.6.5 +CCP_IMAGE_TAG=centos8-13.6-4.6.6 IMAGES=( crunchy-prometheus diff --git a/conf/postgres-operator/pgo.yaml b/conf/postgres-operator/pgo.yaml index 49ca4c412c..cc8016e562 100644 --- a/conf/postgres-operator/pgo.yaml +++ b/conf/postgres-operator/pgo.yaml @@ -2,7 +2,7 @@ Cluster: CCPImagePrefix: registry.developers.crunchydata.com/crunchydata Metrics: false Badger: false - CCPImageTag: centos8-13.5-4.6.5 + CCPImageTag: centos8-13.6-4.6.6 Port: 5432 PGBadgerPort: 10000 ExporterPort: 9187 @@ -80,4 +80,4 @@ Storage: Pgo: Audit: false PGOImagePrefix: registry.developers.crunchydata.com/crunchydata - PGOImageTag: centos8-4.6.5 + PGOImageTag: centos8-4.6.6 diff --git a/docs/config.toml b/docs/config.toml index ecd0b59da7..d89bd7cb26 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -25,13 +25,12 @@ disableNavChevron = false # set true to hide next/prev chevron, default is false highlightClientSide = false # set true to use highlight.pack.js instead of the default hugo chroma highlighter menushortcutsnewtab = true # set true to open shortcuts links to a new tab/window enableGitInfo = true -operatorVersion = "4.6.5" -postgresVersion = "13.5" -postgresVersion13 = "13.5" -postgresVersion12 = "12.9" -postgresVersion11 = "11.14" -postgresVersion10 = "10.19" -postgresVersion96 = "9.6.24" +operatorVersion = "4.6.6" +postgresVersion = "13.6" +postgresVersion13 = "13.6" +postgresVersion12 = "12.10" +postgresVersion11 = "11.15" +postgresVersion10 = "10.20" postgisVersion = "3.0" centosBase = "centos8" diff --git a/docs/content/Configuration/compatibility.md b/docs/content/Configuration/compatibility.md index aa90276db1..65c340f4e4 100644 --- a/docs/content/Configuration/compatibility.md +++ b/docs/content/Configuration/compatibility.md @@ -12,6 +12,11 @@ version dependencies between the two projects. Below are the operator releases a | Operator Release | Container Release | Postgres | PgBackrest Version |:----------|:-------------|:------------|:-------------- +| 4.6.6 | 4.6.6 | 13.6 | 2.31 | +|||12.10|2.31| +|||11.15|2.31| +|||10.20|2.31| +|||| | 4.6.5 | 4.6.5 | 13.5 | 2.31 | |||12.9|2.31| |||11.14|2.31| diff --git a/docs/content/advanced/crunchy-postgres-exporter.md b/docs/content/advanced/crunchy-postgres-exporter.md index aa0f96eadf..acfb83a2e3 100644 --- a/docs/content/advanced/crunchy-postgres-exporter.md +++ b/docs/content/advanced/crunchy-postgres-exporter.md @@ -23,7 +23,7 @@ can be specified for the API to collect. For an example of a queries.yml file, s The crunchy-postgres-exporter Docker image contains the following packages (versions vary depending on PostgreSQL version): -* PostgreSQL ({{< param postgresVersion13 >}}, {{< param postgresVersion12 >}}, {{< param postgresVersion11 >}}, {{< param postgresVersion10 >}}, and {{< param postgresVersion96 >}}. +* PostgreSQL ({{< param postgresVersion13 >}}, {{< param postgresVersion12 >}}, {{< param postgresVersion11 >}}, and {{< param postgresVersion10 >}}. * CentOS 8 - publicly available * UBI 7, UBI 8 - customers only * [PostgreSQL Exporter](https://github.com/wrouesnel/postgres_exporter) diff --git a/examples/create-by-resource/fromcrd.json b/examples/create-by-resource/fromcrd.json index ce66563361..f36bef16f0 100644 --- a/examples/create-by-resource/fromcrd.json +++ b/examples/create-by-resource/fromcrd.json @@ -10,7 +10,7 @@ "deployment-name": "fromcrd", "name": "fromcrd", "pg-cluster": "fromcrd", - "pgo-version": "4.6.5", + "pgo-version": "4.6.6", "pgouser": "pgoadmin" }, "name": "fromcrd", @@ -45,7 +45,7 @@ "supplementalgroups": "" }, "ccpimage": "crunchy-postgres-ha", - "ccpimagetag": "centos8-13.5-4.6.5", + "ccpimagetag": "centos8-13.6-4.6.6", "clustername": "fromcrd", "database": "userdb", "exporterport": "9187", @@ -60,7 +60,7 @@ "port": "5432", "user": "testuser", "userlabels": { - "pgo-version": "4.6.5" + "pgo-version": "4.6.6" } } } diff --git a/examples/envs.sh b/examples/envs.sh index 540623bd86..97903aa02c 100644 --- a/examples/envs.sh +++ b/examples/envs.sh @@ -20,7 +20,7 @@ export PGO_CONF_DIR=$PGOROOT/installers/ansible/roles/pgo-operator/files # the version of the Operator you run is set by these vars export PGO_IMAGE_PREFIX=registry.developers.crunchydata.com/crunchydata export PGO_BASEOS=centos8 -export PGO_VERSION=4.6.5 +export PGO_VERSION=4.6.6 export PGO_IMAGE_TAG=$PGO_BASEOS-$PGO_VERSION # for setting the pgo apiserver port, disabling TLS or not verifying TLS diff --git a/examples/helm/README.md b/examples/helm/README.md index 7a71acc392..55c91e6de4 100644 --- a/examples/helm/README.md +++ b/examples/helm/README.md @@ -64,7 +64,7 @@ The following values can also be set: - `ha`: Whether or not to deploy a high availability PostgreSQL cluster. Can be either `true` or `false`, defaults to `false`. - `imagePrefix`: The prefix of the container images to use for this PostgreSQL cluster. Default to `registry.developers.crunchydata.com/crunchydata`. - `image`: The name of the container image to use for the PostgreSQL cluster. Defaults to `crunchy-postgres-ha`. -- `imageTag`: The container image tag to use. Defaults to `centos8-13.5-4.6.5`. +- `imageTag`: The container image tag to use. Defaults to `centos8-13.6-4.6.6`. - `memory`: The memory limit for the PostgreSQL cluster. Follows standard Kubernetes formatting. - `monitoring`: Whether or not to enable monitoring / metrics collection for this PostgreSQL instance. Can either be `true` or `false`, defaults to `false`. @@ -103,7 +103,7 @@ PGPASSWORD="W4tch0ut4hippo$" psql -h localhost -U hippo hippo ## Notes -Prior to PostgreSQL Operator 4.6.5, you will have to manually clean up some of the artifacts when running `helm uninstall`. +Prior to PostgreSQL Operator 4.6.0, you will have to manually clean up some of the artifacts when running `helm uninstall`. ## Additional Resources diff --git a/examples/helm/postgres/Chart.yaml b/examples/helm/postgres/Chart.yaml index 04afbcd55c..2062a21702 100644 --- a/examples/helm/postgres/Chart.yaml +++ b/examples/helm/postgres/Chart.yaml @@ -20,4 +20,4 @@ version: 0.2.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. -appVersion: 4.6.5 +appVersion: 4.6.6 diff --git a/examples/helm/postgres/templates/pgcluster.yaml b/examples/helm/postgres/templates/pgcluster.yaml index 7920783a9f..d9514198f1 100644 --- a/examples/helm/postgres/templates/pgcluster.yaml +++ b/examples/helm/postgres/templates/pgcluster.yaml @@ -28,7 +28,7 @@ spec: storagetype: dynamic ccpimage: {{ .Values.image | default "crunchy-postgres-ha" | quote }} ccpimageprefix: {{ .Values.imagePrefix | default "registry.developers.crunchydata.com/crunchydata" | quote }} - ccpimagetag: {{ .Values.imageTag | default "centos8-13.5-4.6.5" | quote }} + ccpimagetag: {{ .Values.imageTag | default "centos8-13.6-4.6.6" | quote }} clustername: {{ .Values.name | quote }} database: {{ .Values.name | quote }} {{- if .Values.monitoring }} diff --git a/examples/helm/postgres/values.yaml b/examples/helm/postgres/values.yaml index 8049810c68..0ab73e712b 100644 --- a/examples/helm/postgres/values.yaml +++ b/examples/helm/postgres/values.yaml @@ -10,5 +10,5 @@ password: W4tch0ut4hippo$ # ha: true # imagePrefix: registry.developers.crunchydata.com/crunchydata # image: crunchy-postgres-ha -# imageTag: centos8-13.5-4.6.5 +# imageTag: centos8-13.6-4.6.6 # memory: 1Gi diff --git a/examples/kustomize/createcluster/README.md b/examples/kustomize/createcluster/README.md index 1f1a478230..51990cc1f7 100644 --- a/examples/kustomize/createcluster/README.md +++ b/examples/kustomize/createcluster/README.md @@ -44,13 +44,13 @@ pgo show cluster hippo -n pgo ``` You will see something like this if successful: ``` -cluster : hippo (crunchy-postgres-ha:centos8-13.5-4.6.5) +cluster : hippo (crunchy-postgres-ha:centos8-13.6-4.6.6) pod : hippo-8fb6bd96-j87wq (Running) on gke-xxxx-default-pool-38e946bd-257w (1/1) (primary) pvc: hippo (1Gi) deployment : hippo deployment : hippo-backrest-shared-repo service : hippo - ClusterIP (10.0.56.86) - Ports (2022/TCP, 5432/TCP) - labels : pgo-version=4.6.5 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.6 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata ``` Feel free to run other pgo cli commands on the hippo cluster @@ -79,7 +79,7 @@ pgo show cluster dev-hippo -n pgo ``` You will see something like this if successful: ``` -cluster : dev-hippo (crunchy-postgres-ha:centos8-13.5-4.6.5) +cluster : dev-hippo (crunchy-postgres-ha:centos8-13.6-4.6.6) pod : dev-hippo-588d4cb746-bwrxb (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: dev-hippo (1Gi) deployment : dev-hippo @@ -87,7 +87,7 @@ cluster : dev-hippo (crunchy-postgres-ha:centos8-13.5-4.6.5) deployment : dev-hippo-pgbouncer service : dev-hippo - ClusterIP (10.0.62.87) - Ports (2022/TCP, 5432/TCP) service : dev-hippo-pgbouncer - ClusterIP (10.0.48.120) - Ports (5432/TCP) - labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.6.5 pgouser=admin + labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.6.6 pgouser=admin ``` #### staging The staging overlay will deploy a crunchy postgreSQL cluster with 2 replica's with annotations added @@ -113,7 +113,7 @@ pgo show cluster staging-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : staging-hippo (crunchy-postgres-ha:centos8-13.5-4.6.5) +cluster : staging-hippo (crunchy-postgres-ha:centos8-13.6-4.6.6) pod : staging-hippo-85cf6dcb65-9h748 (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: staging-hippo (1Gi) pod : staging-hippo-lnxw-cf47d8c8b-6r4wn (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (replica) @@ -128,7 +128,7 @@ cluster : staging-hippo (crunchy-postgres-ha:centos8-13.5-4.6.5) service : staging-hippo-replica - ClusterIP (10.0.56.57) - Ports (2022/TCP, 5432/TCP) pgreplica : staging-hippo-lnxw pgreplica : staging-hippo-rpl1 - labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.6.5 pgouser=admin vendor=crunchydata + labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.6.6 pgouser=admin vendor=crunchydata ``` #### production @@ -154,7 +154,7 @@ pgo show cluster prod-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : prod-hippo (crunchy-postgres-ha:centos8-13.5-4.6.5) +cluster : prod-hippo (crunchy-postgres-ha:centos8-13.6-4.6.6) pod : prod-hippo-5d6dd46497-rr67c (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (primary) pvc: prod-hippo (1Gi) pod : prod-hippo-flty-84d97c8769-2pzbh (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (replica) @@ -165,7 +165,7 @@ cluster : prod-hippo (crunchy-postgres-ha:centos8-13.5-4.6.5) service : prod-hippo - ClusterIP (10.0.56.18) - Ports (2022/TCP, 5432/TCP) service : prod-hippo-replica - ClusterIP (10.0.56.101) - Ports (2022/TCP, 5432/TCP) pgreplica : prod-hippo-flty - labels : pgo-version=4.6.5 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.6 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata ``` ### Delete the clusters To delete the clusters run the following pgo cli commands diff --git a/examples/kustomize/createcluster/base/pgcluster.yaml b/examples/kustomize/createcluster/base/pgcluster.yaml index 9d9494d8a2..331bff86bf 100644 --- a/examples/kustomize/createcluster/base/pgcluster.yaml +++ b/examples/kustomize/createcluster/base/pgcluster.yaml @@ -10,7 +10,7 @@ metadata: deployment-name: hippo name: hippo pg-cluster: hippo - pgo-version: 4.6.5 + pgo-version: 4.6.6 pgouser: admin name: hippo namespace: pgo @@ -46,7 +46,7 @@ spec: postgres: {} ccpimage: crunchy-postgres-ha ccpimageprefix: registry.developers.crunchydata.com/crunchydata - ccpimagetag: centos8-13.5-4.6.5 + ccpimagetag: centos8-13.6-4.6.6 clustername: hippo customconfig: "" database: hippo @@ -69,4 +69,4 @@ spec: port: "5432" user: hippo userlabels: - pgo-version: 4.6.5 + pgo-version: 4.6.6 diff --git a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml index 733491df5a..504976ddb5 100644 --- a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml +++ b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml @@ -20,4 +20,4 @@ spec: storagetype: dynamic supplementalgroups: "" userlabels: - pgo-version: 4.6.5 + pgo-version: 4.6.6 diff --git a/installers/ansible/README.md b/installers/ansible/README.md index 8098a59a6a..1014b211ab 100644 --- a/installers/ansible/README.md +++ b/installers/ansible/README.md @@ -4,7 +4,7 @@ PGO: The Postgres Operator from Crunchy Data

-Latest Release: 4.6.5 +Latest Release: 4.6.6 ## General diff --git a/installers/ansible/values.yaml b/installers/ansible/values.yaml index f1cac20070..2697315bb3 100644 --- a/installers/ansible/values.yaml +++ b/installers/ansible/values.yaml @@ -17,7 +17,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.5-4.6.5" +ccp_image_tag: "centos8-13.6-4.6.6" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -49,14 +49,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.5" +pgo_client_version: "4.6.6" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos8-4.6.5" +pgo_image_tag: "centos8-4.6.6" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/gcp-marketplace/Makefile b/installers/gcp-marketplace/Makefile index 850a7a5332..7075f7a8a3 100644 --- a/installers/gcp-marketplace/Makefile +++ b/installers/gcp-marketplace/Makefile @@ -6,7 +6,7 @@ MARKETPLACE_TOOLS ?= gcr.io/cloud-marketplace-tools/k8s/dev:$(MARKETPLACE_VERSIO MARKETPLACE_VERSION ?= 0.9.4 KUBECONFIG ?= $(HOME)/.kube/config PARAMETERS ?= {} -PGO_VERSION ?= 4.6.5 +PGO_VERSION ?= 4.6.6 IMAGE_BUILD_ARGS = --build-arg MARKETPLACE_VERSION='$(MARKETPLACE_VERSION)' \ --build-arg PGO_VERSION='$(PGO_VERSION)' diff --git a/installers/gcp-marketplace/README.md b/installers/gcp-marketplace/README.md index b69405a5ca..7299b0dce0 100644 --- a/installers/gcp-marketplace/README.md +++ b/installers/gcp-marketplace/README.md @@ -59,7 +59,7 @@ Google Cloud Marketplace. ```shell IMAGE_REPOSITORY=gcr.io/crunchydata-public/postgres-operator - export PGO_VERSION=4.6.5 + export PGO_VERSION=4.6.6 export INSTALLER_IMAGE=${IMAGE_REPOSITORY}/deployer:${PGO_VERSION} export OPERATOR_IMAGE=${IMAGE_REPOSITORY}:${PGO_VERSION} export OPERATOR_IMAGE_API=${IMAGE_REPOSITORY}/pgo-apiserver:${PGO_VERSION} diff --git a/installers/gcp-marketplace/values.yaml b/installers/gcp-marketplace/values.yaml index b01da9bff1..1e48c44118 100644 --- a/installers/gcp-marketplace/values.yaml +++ b/installers/gcp-marketplace/values.yaml @@ -10,7 +10,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.5-4.6.5" +ccp_image_tag: "centos8-13.6-4.6.6" create_rbac: "true" db_name: "" db_password_age_days: "0" @@ -32,9 +32,9 @@ pgo_admin_role_name: "pgoadmin" pgo_admin_username: "admin" pgo_client_container_install: "false" pgo_client_install: 'false' -pgo_client_version: "4.6.5" +pgo_client_version: "4.6.6" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.5" +pgo_image_tag: "centos8-4.6.6" pgo_installation_name: '${OPERATOR_NAME}' pgo_operator_namespace: '${OPERATOR_NAMESPACE}' scheduler_timeout: "3600" diff --git a/installers/helm/Chart.yaml b/installers/helm/Chart.yaml index 8ca87255f7..a325335768 100644 --- a/installers/helm/Chart.yaml +++ b/installers/helm/Chart.yaml @@ -3,7 +3,7 @@ name: postgres-operator description: 'PGO: The Postgres Operator from Crunchy Data Helm Chart for Kubernetes' type: application version: 0.2.0 -appVersion: 4.6.5 +appVersion: 4.6.6 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/docs/static/logos/pgo.svg keywords: diff --git a/installers/helm/values.yaml b/installers/helm/values.yaml index 02c4ea37a9..15c264cd32 100644 --- a/installers/helm/values.yaml +++ b/installers/helm/values.yaml @@ -37,7 +37,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.5-4.6.5" +ccp_image_tag: "centos8-13.6-4.6.6" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -69,14 +69,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.5" +pgo_client_version: "4.6.6" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos8-4.6.5" +pgo_image_tag: "centos8-4.6.6" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/kubectl/client-setup.sh b/installers/kubectl/client-setup.sh index 9106c7db9d..f26ea0ff61 100755 --- a/installers/kubectl/client-setup.sh +++ b/installers/kubectl/client-setup.sh @@ -14,7 +14,7 @@ # This script should be run after the operator has been deployed PGO_OPERATOR_NAMESPACE="${PGO_OPERATOR_NAMESPACE:-pgo}" PGO_USER_ADMIN="${PGO_USER_ADMIN:-pgouser-admin}" -PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.5}" +PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.6}" PGO_CLIENT_URL="https://github.com/CrunchyData/postgres-operator/releases/download/${PGO_CLIENT_VERSION}" PGO_CMD="${PGO_CMD-kubectl}" diff --git a/installers/kubectl/postgres-operator-ocp311.yml b/installers/kubectl/postgres-operator-ocp311.yml index c419044b85..47909c6eec 100644 --- a/installers/kubectl/postgres-operator-ocp311.yml +++ b/installers/kubectl/postgres-operator-ocp311.yml @@ -50,7 +50,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos8-13.5-4.6.5" + ccp_image_tag: "centos8-13.6-4.6.6" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -83,14 +83,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.5" + pgo_client_version: "4.6.6" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos8-4.6.5" + pgo_image_tag: "centos8-4.6.6" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -171,7 +171,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.5 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.6 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/kubectl/postgres-operator.yml b/installers/kubectl/postgres-operator.yml index e8bd94a6c8..f1956fde54 100644 --- a/installers/kubectl/postgres-operator.yml +++ b/installers/kubectl/postgres-operator.yml @@ -145,7 +145,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos8-13.5-4.6.5" + ccp_image_tag: "centos8-13.6-4.6.6" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -178,14 +178,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.5" + pgo_client_version: "4.6.6" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos8-4.6.5" + pgo_image_tag: "centos8-4.6.6" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -281,7 +281,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.5 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.6 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/ansible/README.md b/installers/metrics/ansible/README.md index 4e3b1fc372..ccbaf5f625 100644 --- a/installers/metrics/ansible/README.md +++ b/installers/metrics/ansible/README.md @@ -4,7 +4,7 @@ Crunchy Data

-Latest Release: 4.6.5 +Latest Release: 4.6.6 ## General diff --git a/installers/metrics/helm/Chart.yaml b/installers/metrics/helm/Chart.yaml index 1e80dd5686..8dc45f4d75 100644 --- a/installers/metrics/helm/Chart.yaml +++ b/installers/metrics/helm/Chart.yaml @@ -3,6 +3,6 @@ name: postgres-operator-monitoring description: Install for Crunchy PostgreSQL Operator Monitoring type: application version: 0.2.0 -appVersion: 4.6.5 +appVersion: 4.6.6 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/docs/static/logos/pgo.svg diff --git a/installers/metrics/helm/helm_template.yaml b/installers/metrics/helm/helm_template.yaml index 2f52fed1cf..5d24bbd5fe 100644 --- a/installers/metrics/helm/helm_template.yaml +++ b/installers/metrics/helm/helm_template.yaml @@ -20,5 +20,5 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.5" +pgo_image_tag: "centos8-4.6.6" diff --git a/installers/metrics/helm/values.yaml b/installers/metrics/helm/values.yaml index c3dd0cf78c..c7e6017df7 100644 --- a/installers/metrics/helm/values.yaml +++ b/installers/metrics/helm/values.yaml @@ -20,7 +20,7 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.5" +pgo_image_tag: "centos8-4.6.6" # ===================== # Configuration Options diff --git a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml index 0ab376a47b..fcd93e723c 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml @@ -101,7 +101,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.5 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.6 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/kubectl/postgres-operator-metrics.yml b/installers/metrics/kubectl/postgres-operator-metrics.yml index 23f73114ce..18fbfb7b9b 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics.yml @@ -171,7 +171,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.5 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.6 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/olm/Makefile b/installers/olm/Makefile index f9411dd395..9d57a1333f 100644 --- a/installers/olm/Makefile +++ b/installers/olm/Makefile @@ -2,7 +2,7 @@ .SUFFIXES: CCP_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -CCP_PG_FULLVERSION ?= 13.5 +CCP_PG_FULLVERSION ?= 13.6 CCP_POSTGIS_VERSION ?= 3.0 CONTAINER ?= docker KUBECONFIG ?= $(HOME)/.kube/config @@ -11,7 +11,7 @@ OLM_TOOLS ?= registry.localhost:5000/postgres-operator-olm-tools:$(OLM_SDK_VERSI OLM_VERSION ?= 0.15.1 PGO_BASEOS ?= centos8 PGO_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -PGO_VERSION ?= 4.6.5 +PGO_VERSION ?= 4.6.6 PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) CCP_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(PGO_VERSION) CCP_POSTGIS_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(CCP_POSTGIS_VERSION)-$(PGO_VERSION) diff --git a/installers/olm/description.openshift.md b/installers/olm/description.openshift.md index 7a33ebd0de..6290d5545c 100644 --- a/installers/olm/description.openshift.md +++ b/installers/olm/description.openshift.md @@ -188,7 +188,7 @@ spec: annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: ${cluster_image_prefix} - ccpimagetag: centos8-13.5-${PGO_VERSION} + ccpimagetag: centos8-13.6-${PGO_VERSION} clustername: ${pgo_cluster_name} database: ${pgo_cluster_name} exporterport: "9187" diff --git a/installers/olm/description.upstream.md b/installers/olm/description.upstream.md index a4da9dc171..d907711b81 100644 --- a/installers/olm/description.upstream.md +++ b/installers/olm/description.upstream.md @@ -168,7 +168,7 @@ spec: annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: ${cluster_image_prefix} - ccpimagetag: centos8-13.5-${PGO_VERSION} + ccpimagetag: centos8-13.6-${PGO_VERSION} clustername: ${pgo_cluster_name} database: ${pgo_cluster_name} exporterport: "9187" diff --git a/pkg/apis/crunchydata.com/v1/doc.go b/pkg/apis/crunchydata.com/v1/doc.go index 57d2cf8d79..9fa7361e77 100644 --- a/pkg/apis/crunchydata.com/v1/doc.go +++ b/pkg/apis/crunchydata.com/v1/doc.go @@ -53,7 +53,7 @@ cluster. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.5", + '{"ClientVersion":"4.6.6", "Namespace":"pgouser1", "Name":"mycluster", $PGO_APISERVER_URL/clusters @@ -72,7 +72,7 @@ show all of the clusters that are in the given namespace. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.5", + '{"ClientVersion":"4.6.6", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/showclusters @@ -82,7 +82,7 @@ $PGO_APISERVER_URL/showclusters curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.5", + '{"ClientVersion":"4.6.6", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/clustersdelete @@ -90,7 +90,7 @@ $PGO_APISERVER_URL/clustersdelete Schemes: http, https BasePath: / - Version: 4.6.5 + Version: 4.6.6 License: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0 Contact: Crunchy Data https://www.crunchydata.com/ diff --git a/pkg/apiservermsgs/common.go b/pkg/apiservermsgs/common.go index f72690b593..c65c29685d 100644 --- a/pkg/apiservermsgs/common.go +++ b/pkg/apiservermsgs/common.go @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -const PGO_VERSION = "4.6.5" +const PGO_VERSION = "4.6.6" // Ok status const Ok = "ok" diff --git a/redhat/atomic/help.1 b/redhat/atomic/help.1 index adb6354b97..3d693281d3 100644 --- a/redhat/atomic/help.1 +++ b/redhat/atomic/help.1 @@ -56,4 +56,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa \fB\fCRelease=\fR .PP -The specific release number of the container. For example, Release="4.6.5" +The specific release number of the container. For example, Release="4.6.6" diff --git a/redhat/atomic/help.md b/redhat/atomic/help.md index a9ef8e7541..247f64c7d3 100644 --- a/redhat/atomic/help.md +++ b/redhat/atomic/help.md @@ -45,4 +45,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa `Release=` -The specific release number of the container. For example, Release="4.6.5" +The specific release number of the container. For example, Release="4.6.6" From 92ab1c93c5c275df92f594839ffa75d1acea14ab Mon Sep 17 00:00:00 2001 From: Andrew L'Ecuyer Date: Tue, 22 Feb 2022 20:57:16 +0000 Subject: [PATCH 253/276] Add PGO v4.6.6 Release Notes [sc-13826] --- docs/content/releases/4.6.6.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 docs/content/releases/4.6.6.md diff --git a/docs/content/releases/4.6.6.md b/docs/content/releases/4.6.6.md new file mode 100644 index 0000000000..5d8577acce --- /dev/null +++ b/docs/content/releases/4.6.6.md @@ -0,0 +1,18 @@ +--- +title: "4.6.6" +date: +draft: false +weight: 54 +--- + +Crunchy Data announces the release of PGO, the Postgres Operator 4.6.6. + +The PostgreSQL Operator is released in conjunction with the [Crunchy Container Suite](https://github.com/CrunchyData/crunchy-containers/). + +PostgreSQL Operator 4.6.6 release includes the following software versions upgrades: + +- [PostgreSQL](https://www.postgresql.org) versions 13.6, 12.10, 11.15 and 10.20 are now available. + +## Changes + +- The version of Go utilized to build `yq` is now aligned with all other Go binaries. From e4f50d6794cd289b8c28a6faf5249e58f7171ffa Mon Sep 17 00:00:00 2001 From: Andrew L'Ecuyer Date: Tue, 22 Feb 2022 20:58:01 +0000 Subject: [PATCH 254/276] Run 'go mod tidy' [sc-13826] --- go.mod | 2 ++ go.sum | 40 ++++++++++------------------------------ 2 files changed, 12 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index c2142d2be5..d81590e1ce 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/sirupsen/logrus v1.5.0 github.com/spf13/cobra v0.0.5 github.com/spf13/pflag v1.0.5 + github.com/spf13/viper v1.3.2 github.com/xdg/stringprep v1.0.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.13.0 go.opentelemetry.io/otel v0.13.0 @@ -20,6 +21,7 @@ require ( go.opentelemetry.io/otel/exporters/trace/jaeger v0.13.0 golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 + golang.org/x/tools v0.0.0-20200904185747-39188db58858 k8s.io/api v0.20.1 k8s.io/apimachinery v0.20.1 k8s.io/client-go v0.20.1 diff --git a/go.sum b/go.sum index 41050fa343..4f3991f354 100644 --- a/go.sum +++ b/go.sum @@ -48,6 +48,7 @@ github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6L github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/sketches-go v0.0.1 h1:RtG+76WKgZuz6FIaGsjoPePmadDBkuD/KC6+ZWu78b8= @@ -89,7 +90,6 @@ github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -123,7 +123,6 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -136,7 +135,6 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0 h1:QvGt2nLcHH0WK9orKa+ppBPAxREcH364nPUedEpK0TY= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= @@ -216,7 +214,6 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= @@ -228,7 +225,6 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -248,7 +244,6 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -272,6 +267,7 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -296,7 +292,6 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -304,6 +299,7 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -321,9 +317,9 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= @@ -351,6 +347,7 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -361,24 +358,19 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.11 h1:DhHlBtkHWPYi8O2y31JkK0TF+DGM+51OopZjH/Ia5qI= github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -387,17 +379,21 @@ github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -405,7 +401,6 @@ github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -459,7 +454,6 @@ golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -525,13 +519,10 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -539,7 +530,6 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 h1:ld7aEMNHoBnnDAX15v1T6z31v8HwR2A9FYOuAhWqkwc= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= @@ -549,7 +539,6 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -597,7 +586,6 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f h1:Fqb3ao1hUmOR3GkUOg/Y+BadLwykBIzs5q8Ez2SbHyc= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY= @@ -607,14 +595,12 @@ golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fq golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -665,14 +651,13 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858 h1:xLt+iB5ksWcZVxqc+g9K41ZHy+6MKWfXCDsjSThnsPA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0= gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -696,7 +681,6 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= @@ -750,10 +734,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= @@ -792,7 +774,6 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI= k8s.io/api v0.20.1 h1:ud1c3W3YNzGd6ABJlbFfKXBKXO+1KdGfcgGGNgFR03E= k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= -k8s.io/apiextensions-apiserver v0.18.6 h1:vDlk7cyFsDyfwn2rNAO2DbmUbvXy5yT5GE3rrqOzaMo= k8s.io/apiextensions-apiserver v0.18.6/go.mod h1:lv89S7fUysXjLZO7ke783xOwVTm6lKizADfvUM/SS/M= k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= k8s.io/apimachinery v0.20.1 h1:LAhz8pKbgR8tUwn7boK+b2HZdt7MiTu2mkYtFMUjTRQ= @@ -810,7 +791,6 @@ k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUc k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= -k8s.io/klog/v2 v2.0.0 h1:Foj74zO6RbjjP4hBEKjnYtjjAhGg4jNynUdYF6fJrok= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.4.0 h1:7+X0fUguPyrKEC4WjH8iGDg3laWgMo5tMnRTIGTTxGQ= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= From b86dc7420d5ce4f1ad4821b34223eed6fcff40c4 Mon Sep 17 00:00:00 2001 From: Andrew L'Ecuyer Date: Wed, 23 Feb 2022 18:18:01 +0000 Subject: [PATCH 255/276] Update client-go to Version v0.21.10 [sc-13826] --- go.mod | 13 ++++---- go.sum | 101 ++++++++++++++++++++++++++++++++++----------------------- 2 files changed, 67 insertions(+), 47 deletions(-) diff --git a/go.mod b/go.mod index d81590e1ce..2e48258e16 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/crunchydata/postgres-operator go 1.15 require ( - github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect github.com/fatih/color v1.9.0 github.com/gorilla/mux v1.7.4 github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect @@ -19,12 +18,12 @@ require ( go.opentelemetry.io/otel v0.13.0 go.opentelemetry.io/otel/exporters/stdout v0.13.0 go.opentelemetry.io/otel/exporters/trace/jaeger v0.13.0 - golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 - golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 - golang.org/x/tools v0.0.0-20200904185747-39188db58858 - k8s.io/api v0.20.1 - k8s.io/apimachinery v0.20.1 - k8s.io/client-go v0.20.1 + golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 + golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 + golang.org/x/tools v0.0.0-20210106214847-113979e3529a + k8s.io/api v0.21.10 + k8s.io/apimachinery v0.21.10 + k8s.io/client-go v0.21.10 sigs.k8s.io/controller-runtime v0.6.4 sigs.k8s.io/yaml v1.2.0 ) diff --git a/go.sum b/go.sum index 4f3991f354..982ddcbf42 100644 --- a/go.sum +++ b/go.sum @@ -34,15 +34,13 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= +github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= -github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= @@ -92,6 +90,7 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -100,8 +99,6 @@ github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c h1:ZfSZ3P3BedhKGUhzj7BQlPSU4OvT6tfOKe3DVHzOA7s= -github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -136,8 +133,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0 h1:QvGt2nLcHH0WK9orKa+ppBPAxREcH364nPUedEpK0TY= -github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= @@ -185,8 +182,9 @@ github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85n github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -215,8 +213,8 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -228,8 +226,9 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -259,6 +258,7 @@ github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= @@ -287,18 +287,19 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -321,6 +322,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -331,6 +334,8 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nsqio/go-nsq v1.0.8 h1:3L2F8tNLlwXXlp2slDUrUWSBn2O3nMh8R1/KEDFTHPk= github.com/nsqio/go-nsq v1.0.8/go.mod h1:vKq36oyeVXgsS5Q8YEO7WghqidAVXQlcFxzQbQTuDEY= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= @@ -455,8 +460,9 @@ golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -524,8 +530,9 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrkoV51/tl5vOWqkcPGvY= +golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -540,8 +547,9 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -588,22 +596,28 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY= -golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887 h1:dXfMednGJh/SUUFjTLsWJz3P+TQt9qnR11GgeI3vWKs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= -golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -648,11 +662,13 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858 h1:xLt+iB5ksWcZVxqc+g9K41ZHy+6MKWfXCDsjSThnsPA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a h1:CB3a9Nez8M13wwlr/E2YtwoU+qYHKfC+JrDa45RXXoQ= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -737,13 +753,16 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= @@ -759,8 +778,9 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= @@ -772,16 +792,16 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI= -k8s.io/api v0.20.1 h1:ud1c3W3YNzGd6ABJlbFfKXBKXO+1KdGfcgGGNgFR03E= -k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= +k8s.io/api v0.21.10 h1:WKcYyNBZNMrE9yejBs0Lx70jGsOW8uUwkiA4ioxkz1Q= +k8s.io/api v0.21.10/go.mod h1:5kqv2pCXwcrOvV12WhVAtLZUKaM0kyrZ6nHObw8SojA= k8s.io/apiextensions-apiserver v0.18.6/go.mod h1:lv89S7fUysXjLZO7ke783xOwVTm6lKizADfvUM/SS/M= k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= -k8s.io/apimachinery v0.20.1 h1:LAhz8pKbgR8tUwn7boK+b2HZdt7MiTu2mkYtFMUjTRQ= -k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/apimachinery v0.21.10 h1:mOStSZoCrsxnAMIm5UtCNn6P328cJAhtzJToQYFsylc= +k8s.io/apimachinery v0.21.10/go.mod h1:USs+ifLG6ZUgHGA/9lGxjdHzCB3hUO3fG1VBOwi0IHo= k8s.io/apiserver v0.18.6/go.mod h1:Zt2XvTHuaZjBz6EFYzpp+X4hTmgWGy8AthNVnTdm3Wg= k8s.io/client-go v0.18.6/go.mod h1:/fwtGLjYMS1MaM5oi+eXhKwG+1UHidUEXRh6cNsdO0Q= -k8s.io/client-go v0.20.1 h1:Qquik0xNFbK9aUG92pxHYsyfea5/RPO9o9bSywNor+M= -k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= +k8s.io/client-go v0.21.10 h1:/AKJEgLpQDWvZbq7cq2vEx0bpqpAlOOHitOrctSV8bI= +k8s.io/client-go v0.21.10/go.mod h1:nAGhVCjwhbDP2whk65n3STSCn24H/VGp1pKSk9UszU8= k8s.io/code-generator v0.18.6/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= k8s.io/component-base v0.18.6/go.mod h1:knSVsibPR5K6EW2XOjEHik6sdU5nCvKMrzMt2D4In14= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= @@ -792,15 +812,15 @@ k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.4.0 h1:7+X0fUguPyrKEC4WjH8iGDg3laWgMo5tMnRTIGTTxGQ= -k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM= +k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= -k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd h1:sOHNzJIkytDF6qadMNKhhDRpc6ODik8lVC6nOur7B2c= -k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= +k8s.io/kube-openapi v0.0.0-20211110012726-3cc51fd1e909 h1:s77MRc/+/eQjsF89MB12JssAlsoi9mnNoaacRqibeAU= +k8s.io/kube-openapi v0.0.0-20211110012726-3cc51fd1e909/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20201110183641-67b214c5f920 h1:CbnUZsM497iRC5QMVkHwyl8s2tB3g7yaSHkYPkpgelw= -k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210521133846-da695404a2bc h1:dx6VGe+PnOW/kD/2UV4aUSsRfJGd7+lcqgJ6Xg0HwUs= +k8s.io/utils v0.0.0-20210521133846-da695404a2bc/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= @@ -809,8 +829,9 @@ sigs.k8s.io/controller-runtime v0.6.4 h1:4013CKsBs5bEqo+LevzDett+LLxag/FjQWG94nV sigs.k8s.io/controller-runtime v0.6.4/go.mod h1:WlZNXcM0++oyaQt4B7C2lEE5JYRs8vJUzRP4N4JpdAY= sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= -sigs.k8s.io/structured-merge-diff/v4 v4.0.2 h1:YHQV7Dajm86OuqnIR6zAelnDWBRjo+YhYV9PmGrh1s8= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= +sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= From 9b4a944e37e3567cd7a2e2c5ee5fa940972a13c1 Mon Sep 17 00:00:00 2001 From: Val Date: Fri, 27 May 2022 17:30:05 -0400 Subject: [PATCH 256/276] update for v4.6.7 and removed centos 8 from docs, examples, (#3228) and makefile [sc-14635] --- Makefile | 6 +-- README.md | 2 +- bin/push-ccp-to-gcr.sh | 2 +- conf/postgres-operator/pgo.yaml | 4 +- docs/config.toml | 14 ++--- docs/content/Configuration/compatibility.md | 5 ++ .../Configuration/pgo-yaml-configuration.md | 2 +- docs/content/Upgrade/automatedupgrade.md | 2 +- docs/content/Upgrade/manual/upgrade35.md | 2 +- docs/content/Upgrade/manual/upgrade4.md | 2 +- .../advanced/crunchy-postgres-exporter.md | 2 +- .../multi-cluster-kubernetes.md | 2 +- docs/content/contributing/developer-setup.md | 2 +- docs/content/custom-resources/_index.md | 8 +-- docs/content/installation/configuration.md | 4 +- .../metrics/metrics-configuration.md | 2 +- docs/content/pgo-client/common-tasks.md | 8 +-- docs/content/releases/4.6.7.md | 17 ++++++ docs/content/support/_index.md | 2 +- examples/create-by-resource/fromcrd.json | 6 +-- examples/envs.sh | 4 +- examples/helm/README.md | 2 +- examples/helm/postgres/Chart.yaml | 2 +- .../helm/postgres/templates/pgcluster.yaml | 2 +- examples/helm/postgres/values.yaml | 2 +- examples/kustomize/createcluster/README.md | 16 +++--- .../createcluster/base/pgcluster.yaml | 6 +-- .../overlay/staging/hippo-rpl1-pgreplica.yaml | 2 +- installers/ansible/README.md | 2 +- installers/ansible/values.yaml | 6 +-- installers/gcp-marketplace/Makefile | 2 +- installers/gcp-marketplace/README.md | 2 +- installers/gcp-marketplace/values.yaml | 6 +-- installers/helm/Chart.yaml | 2 +- installers/helm/values.yaml | 6 +-- installers/kubectl/client-setup.sh | 2 +- .../kubectl/postgres-operator-ocp311.yml | 8 +-- installers/kubectl/postgres-operator.yml | 8 +-- installers/metrics/ansible/README.md | 2 +- installers/metrics/helm/Chart.yaml | 2 +- installers/metrics/helm/helm_template.yaml | 2 +- installers/metrics/helm/values.yaml | 2 +- .../postgres-operator-metrics-ocp311.yml | 2 +- .../kubectl/postgres-operator-metrics.yml | 2 +- installers/olm/Makefile | 6 +-- installers/olm/description.openshift.md | 2 +- installers/olm/description.upstream.md | 2 +- internal/util/util_test.go | 54 +++++++++---------- pkg/apis/crunchydata.com/v1/doc.go | 8 +-- pkg/apiservermsgs/common.go | 2 +- redhat/atomic/help.1 | 2 +- redhat/atomic/help.md | 2 +- 52 files changed, 143 insertions(+), 121 deletions(-) create mode 100644 docs/content/releases/4.6.7.md diff --git a/Makefile b/Makefile index a71f00dc47..8f4a2373d7 100644 --- a/Makefile +++ b/Makefile @@ -2,12 +2,12 @@ # Default values if not already set ANSIBLE_VERSION ?= 2.9.* PGOROOT ?= $(CURDIR) -PGO_BASEOS ?= centos8 +PGO_BASEOS ?= ubi8 PGO_IMAGE_PREFIX ?= crunchydata PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) -PGO_VERSION ?= 4.6.6 +PGO_VERSION ?= 4.6.7 PGO_PG_VERSION ?= 13 -PGO_PG_FULLVERSION ?= 13.6 +PGO_PG_FULLVERSION ?= 13.7 PGO_BACKREST_VERSION ?= 2.31 PACKAGER ?= yum diff --git a/README.md b/README.md index 20f25a3cd5..8510ec3f01 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ to start as quickly as: ```shell kubectl create namespace pgo -kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.6/installers/kubectl/postgres-operator.yml +kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.7/installers/kubectl/postgres-operator.yml ``` Otherwise, we highly recommend following the instructions from our [Quickstart](https://access.crunchydata.com/documentation/postgres-operator/latest/quickstart/). diff --git a/bin/push-ccp-to-gcr.sh b/bin/push-ccp-to-gcr.sh index 563311442c..0b7141409e 100755 --- a/bin/push-ccp-to-gcr.sh +++ b/bin/push-ccp-to-gcr.sh @@ -16,7 +16,7 @@ GCR_IMAGE_PREFIX=gcr.io/crunchy-dev-test CCP_IMAGE_PREFIX=crunchydata -CCP_IMAGE_TAG=centos8-13.6-4.6.6 +CCP_IMAGE_TAG=ubi8-13.7-4.6.7 IMAGES=( crunchy-prometheus diff --git a/conf/postgres-operator/pgo.yaml b/conf/postgres-operator/pgo.yaml index cc8016e562..a833f68721 100644 --- a/conf/postgres-operator/pgo.yaml +++ b/conf/postgres-operator/pgo.yaml @@ -2,7 +2,7 @@ Cluster: CCPImagePrefix: registry.developers.crunchydata.com/crunchydata Metrics: false Badger: false - CCPImageTag: centos8-13.6-4.6.6 + CCPImageTag: ubi8-13.7-4.6.7 Port: 5432 PGBadgerPort: 10000 ExporterPort: 9187 @@ -80,4 +80,4 @@ Storage: Pgo: Audit: false PGOImagePrefix: registry.developers.crunchydata.com/crunchydata - PGOImageTag: centos8-4.6.6 + PGOImageTag: ubi8-4.6.7 diff --git a/docs/config.toml b/docs/config.toml index d89bd7cb26..8175bc86c1 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -25,14 +25,14 @@ disableNavChevron = false # set true to hide next/prev chevron, default is false highlightClientSide = false # set true to use highlight.pack.js instead of the default hugo chroma highlighter menushortcutsnewtab = true # set true to open shortcuts links to a new tab/window enableGitInfo = true -operatorVersion = "4.6.6" -postgresVersion = "13.6" -postgresVersion13 = "13.6" -postgresVersion12 = "12.10" -postgresVersion11 = "11.15" -postgresVersion10 = "10.20" +operatorVersion = "4.6.7" +postgresVersion = "13.7" +postgresVersion13 = "13.7" +postgresVersion12 = "12.11" +postgresVersion11 = "11.16" +postgresVersion10 = "10.21" postgisVersion = "3.0" -centosBase = "centos8" +ubiBase = "ubi8" [outputs] home = [ "HTML", "RSS", "JSON"] diff --git a/docs/content/Configuration/compatibility.md b/docs/content/Configuration/compatibility.md index 65c340f4e4..3c21881ba4 100644 --- a/docs/content/Configuration/compatibility.md +++ b/docs/content/Configuration/compatibility.md @@ -12,6 +12,11 @@ version dependencies between the two projects. Below are the operator releases a | Operator Release | Container Release | Postgres | PgBackrest Version |:----------|:-------------|:------------|:-------------- +| 4.6.7 | 4.6.7 | 13.7 | 2.31 | +|||12.11|2.31| +|||11.16|2.31| +|||10.21|2.31| +|||| | 4.6.6 | 4.6.6 | 13.6 | 2.31 | |||12.10|2.31| |||11.15|2.31| diff --git a/docs/content/Configuration/pgo-yaml-configuration.md b/docs/content/Configuration/pgo-yaml-configuration.md index 63f5812c71..1faebfd60f 100644 --- a/docs/content/Configuration/pgo-yaml-configuration.md +++ b/docs/content/Configuration/pgo-yaml-configuration.md @@ -16,7 +16,7 @@ The *pgo.yaml* file is broken into major sections as described below: |---|---| |BasicAuth | If set to `"true"` will enable Basic Authentication. If set to `"false"`, will allow a valid Operator user to successfully authenticate regardless of the value of the password provided for Basic Authentication. Defaults to `"true".` |CCPImagePrefix |newly created containers will be based on this image prefix (e.g. crunchydata), update this if you require a custom image prefix -|CCPImageTag |newly created containers will be based on this image version (e.g. {{< param centosBase >}}-{{< param postgresVersion >}}-{{< param operatorVersion >}}), unless you override it using the --ccp-image-tag command line flag +|CCPImageTag |newly created containers will be based on this image version (e.g. {{< param ubiBase >}}-{{< param postgresVersion >}}-{{< param operatorVersion >}}), unless you override it using the --ccp-image-tag command line flag |Port | the PostgreSQL port to use for new containers (e.g. 5432) |PGBadgerPort | the port used to connect to pgbadger (e.g. 10000) |ExporterPort | the port used to connect to postgres exporter (e.g. 9187) diff --git a/docs/content/Upgrade/automatedupgrade.md b/docs/content/Upgrade/automatedupgrade.md index 31480bc07e..72184ced6a 100644 --- a/docs/content/Upgrade/automatedupgrade.md +++ b/docs/content/Upgrade/automatedupgrade.md @@ -127,7 +127,7 @@ pgo upgrade mycluster If you are using the PostGIS-enabled image (i.e. `crunchy-postgres-gis-ha`) or any other custom images, you will need to add the `--ccp-image-tag`: ``` -pgo upgrade --ccp-image-tag={{< param centosBase >}}-{{< param postgresVersion >}}-{{< param postgisVersion >}}-{{< param operatorVersion >}} mygiscluster +pgo upgrade --ccp-image-tag={{< param ubiBase >}}-{{< param postgresVersion >}}-{{< param postgisVersion >}}-{{< param operatorVersion >}} mygiscluster ``` Where `{{< param postgresVersion >}}` is the PostgreSQL version, `{{< param postgisVersion >}}` is the PostGIS version and `{{< param operatorVersion >}}` is the PostgreSQL Operator version. Please note, no tag validation will be performed and additional steps may be required to upgrade your PostGIS extension implementation. For more information on PostGIS upgrade considerations, please see diff --git a/docs/content/Upgrade/manual/upgrade35.md b/docs/content/Upgrade/manual/upgrade35.md index 53704d6bf6..e0a678bfc0 100644 --- a/docs/content/Upgrade/manual/upgrade35.md +++ b/docs/content/Upgrade/manual/upgrade35.md @@ -17,7 +17,7 @@ A major change to this container is that the PostgreSQL process is now managed b When creating your new clusters using version {{< param operatorVersion >}} of the PostgreSQL Operator, the `pgo create cluster` command will automatically use the new `crunchy-postgres-ha` image if the image is unspecified. If you are creating a PostGIS enabled cluster, please be sure to use the updated image name and image tag, as with the command: ``` -pgo create cluster mygiscluster --ccp-image=crunchy-postgres-gis-ha --ccp-image-tag={{< param centosBase >}}-{{< param postgresVersion >}}-{{< param postgisVersion >}}-{{< param operatorVersion >}} +pgo create cluster mygiscluster --ccp-image=crunchy-postgres-gis-ha --ccp-image-tag={{< param ubiBase >}}-{{< param postgresVersion >}}-{{< param postgisVersion >}}-{{< param operatorVersion >}} ``` Where `{{< param postgresVersion >}}` is the PostgreSQL version, `{{< param postgisVersion >}}` is the PostGIS version and `{{< param operatorVersion >}}` is the PostgreSQL Operator version. Please note, no tag validation will be performed and additional steps may be required to upgrade your PostGIS extension implementation. For more information on PostGIS upgrade considerations, please see diff --git a/docs/content/Upgrade/manual/upgrade4.md b/docs/content/Upgrade/manual/upgrade4.md index f39439cfc6..d8fb7a579e 100644 --- a/docs/content/Upgrade/manual/upgrade4.md +++ b/docs/content/Upgrade/manual/upgrade4.md @@ -19,7 +19,7 @@ A major change to this container is that the PostgreSQL process is now managed b When creating your new clusters using version {{< param operatorVersion >}} of the PostgreSQL Operator, the `pgo create cluster` command will automatically use the new `crunchy-postgres-ha` image if the image is unspecified. If you are creating a PostGIS enabled cluster, please be sure to use the updated image name and image tag, as with the command: ``` -pgo create cluster mygiscluster --ccp-image=crunchy-postgres-gis-ha --ccp-image-tag={{< param centosBase >}}-{{< param postgresVersion >}}-{{< param postgisVersion >}}-{{< param operatorVersion >}} +pgo create cluster mygiscluster --ccp-image=crunchy-postgres-gis-ha --ccp-image-tag={{< param ubiBase >}}-{{< param postgresVersion >}}-{{< param postgisVersion >}}-{{< param operatorVersion >}} ``` Where `{{< param postgresVersion >}}` is the PostgreSQL version, `{{< param postgisVersion >}}` is the PostGIS version and `{{< param operatorVersion >}}` is the PostgreSQL Operator version. Please note, no tag validation will be performed and additional steps may be required to upgrade your PostGIS extension implementation. For more information on PostGIS upgrade considerations, please see diff --git a/docs/content/advanced/crunchy-postgres-exporter.md b/docs/content/advanced/crunchy-postgres-exporter.md index acfb83a2e3..bc31829bc5 100644 --- a/docs/content/advanced/crunchy-postgres-exporter.md +++ b/docs/content/advanced/crunchy-postgres-exporter.md @@ -24,7 +24,7 @@ can be specified for the API to collect. For an example of a queries.yml file, s The crunchy-postgres-exporter Docker image contains the following packages (versions vary depending on PostgreSQL version): * PostgreSQL ({{< param postgresVersion13 >}}, {{< param postgresVersion12 >}}, {{< param postgresVersion11 >}}, and {{< param postgresVersion10 >}}. -* CentOS 8 - publicly available +* UBI 8 - publicly available * UBI 7, UBI 8 - customers only * [PostgreSQL Exporter](https://github.com/wrouesnel/postgres_exporter) diff --git a/docs/content/architecture/high-availability/multi-cluster-kubernetes.md b/docs/content/architecture/high-availability/multi-cluster-kubernetes.md index 0ce2960a83..c84b5ad35b 100644 --- a/docs/content/architecture/high-availability/multi-cluster-kubernetes.md +++ b/docs/content/architecture/high-availability/multi-cluster-kubernetes.md @@ -219,7 +219,7 @@ command. ``` pgo show cluster hippo -cluster : standby (crunchy-postgres-ha:{{< param centosBase >}}-{{< param postgresVersion >}}-{{< param operatorVersion >}}) +cluster : standby (crunchy-postgres-ha:{{< param ubiBase >}}-{{< param postgresVersion >}}-{{< param operatorVersion >}}) standby : true ``` ## Promoting a Standby Cluster diff --git a/docs/content/contributing/developer-setup.md b/docs/content/contributing/developer-setup.md index 84ea155636..3525f4374c 100644 --- a/docs/content/contributing/developer-setup.md +++ b/docs/content/contributing/developer-setup.md @@ -12,7 +12,7 @@ This guide is intended for those wanting to build the Operator from source or co # Prerequisites -The target development host for these instructions is a CentOS 8 or RHEL 8 host. Others operating systems +The target development host for these instructions is a RHEL 8 host. Others operating systems are possible, however we do not support building or running the Operator on others at this time. ## Environment Variables diff --git a/docs/content/custom-resources/_index.md b/docs/content/custom-resources/_index.md index 4e92297ed7..17f9d469f7 100644 --- a/docs/content/custom-resources/_index.md +++ b/docs/content/custom-resources/_index.md @@ -107,7 +107,7 @@ spec: annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: registry.developers.crunchydata.com/crunchydata - ccpimagetag: {{< param centosBase >}}-{{< param postgresVersion >}}-{{< param operatorVersion >}} + ccpimagetag: {{< param ubiBase >}}-{{< param postgresVersion >}}-{{< param operatorVersion >}} clustername: ${pgo_cluster_name} database: ${pgo_cluster_name} exporterport: "9187" @@ -302,7 +302,7 @@ spec: backrestS3VerifyTLS: "" ccpimage: crunchy-postgres-ha ccpimageprefix: registry.developers.crunchydata.com/crunchydata - ccpimagetag: {{< param centosBase >}}-{{< param postgresVersion >}}-{{< param operatorVersion >}} + ccpimagetag: {{< param ubiBase >}}-{{< param postgresVersion >}}-{{< param operatorVersion >}} clustername: ${pgo_cluster_name} database: ${pgo_cluster_name} exporterport: "9187" @@ -422,7 +422,7 @@ spec: annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: registry.developers.crunchydata.com/crunchydata - ccpimagetag: {{< param centosBase >}}-{{< param postgresVersion >}}-{{< param operatorVersion >}} + ccpimagetag: {{< param ubiBase >}}-{{< param postgresVersion >}}-{{< param operatorVersion >}} clustername: ${pgo_cluster_name} database: ${pgo_cluster_name} exporterport: "9187" @@ -739,7 +739,7 @@ make changes, as described below. | backrestStorageTypes | `create` | An optional parameter that takes an array of different repositories types that can be used to store pgBackRest backups. Choices are `posix` and `s3`. If nothing is specified, it defaults to `posix`. (`local`, equivalent to `posix`, is available for backwards compatibility).| | ccpimage | `create` | The name of the PostgreSQL container image to use, e.g. `crunchy-postgres-ha` or `crunchy-postgres-ha-gis`. | | ccpimageprefix | `create` | If provided, the image prefix (or registry) of the PostgreSQL container image, e.g. `registry.developers.crunchydata.com/crunchydata`. The default is to use the image prefix set in the PostgreSQL Operator configuration. | -| ccpimagetag | `create` | The tag of the PostgreSQL container image to use, e.g. `{{< param centosBase >}}-{{< param postgresVersion >}}-{{< param operatorVersion >}}`. | +| ccpimagetag | `create` | The tag of the PostgreSQL container image to use, e.g. `{{< param ubiBase >}}-{{< param postgresVersion >}}-{{< param operatorVersion >}}`. | | clustername | `create` | The name of the PostgreSQL cluster, e.g. `hippo`. This is used to group PostgreSQL instances (primary, replicas) together. | | customconfig | `create` | If specified, references a custom ConfigMap to use when bootstrapping a PostgreSQL cluster. For the shape of this file, please see the section on [Custom Configuration]({{< relref "/advanced/custom-configuration.md" >}}) | | database | `create` | The name of a database that the PostgreSQL user can log into after the PostgreSQL cluster is created. | diff --git a/docs/content/installation/configuration.md b/docs/content/installation/configuration.md index 5d31f2453f..2368a2e621 100644 --- a/docs/content/installation/configuration.md +++ b/docs/content/installation/configuration.md @@ -31,7 +31,7 @@ Operator. | `ccp_image_prefix` | registry.developers.crunchydata.com/crunchydata | **Required** | Configures the image prefix used when creating containers from Crunchy Container Suite. | | `ccp_image_pull_secret` | | | Name of a Secret containing credentials for container image registries. | | `ccp_image_pull_secret_manifest` | | | Provide a path to the Secret manifest to be installed in each namespace. (optional) | -| `ccp_image_tag` | {{< param centosBase >}}-{{< param postgresVersion >}}-{{< param operatorVersion >}} | **Required** | Configures the image tag (version) used when creating containers from Crunchy Container Suite. | +| `ccp_image_tag` | {{< param ubiBase >}}-{{< param postgresVersion >}}-{{< param operatorVersion >}} | **Required** | Configures the image tag (version) used when creating containers from Crunchy Container Suite. | | `create_rbac` | true | **Required** | Set to true if the installer should create the RBAC resources required to run the PostgreSQL Operator. | | `crunchy_debug` | false | | Set to configure Operator to use debugging mode. Note: this can cause sensitive data such as passwords to appear in Operator logs. | | `db_name` | | | Set to a value to configure the default database name on all newly created clusters. By default, the PostgreSQL Operator will set it to the name of the cluster that is being created. | @@ -69,7 +69,7 @@ Operator. | `pgo_image_prefix` | registry.developers.crunchydata.com/crunchydata | **Required** | Configures the image prefix used when creating containers for the Crunchy PostgreSQL Operator (apiserver, operator, scheduler..etc). | | `pgo_image_pull_secret` | | | Name of a Secret containing credentials for container image registries. | | `pgo_image_pull_secret_manifest` | | | Provide a path to the Secret manifest to be installed in each namespace. (optional) | -| `pgo_image_tag` | {{< param centosBase >}}-{{< param operatorVersion >}} | **Required** | Configures the image tag used when creating containers for the Crunchy PostgreSQL Operator (apiserver, operator, scheduler..etc) | +| `pgo_image_tag` | {{< param ubiBase >}}-{{< param operatorVersion >}} | **Required** | Configures the image tag used when creating containers for the Crunchy PostgreSQL Operator (apiserver, operator, scheduler..etc) | | `pgo_installation_name` | devtest | **Required** | The name of the PGO installation. | | `pgo_noauth_routes` | | | Configures URL routes with mTLS and HTTP BasicAuth disabled. | | `pgo_operator_namespace` | pgo | **Required** | Set to configure the namespace where Operator will be deployed. | diff --git a/docs/content/installation/metrics/metrics-configuration.md b/docs/content/installation/metrics/metrics-configuration.md index e90a7ff88b..cc3e905aa4 100644 --- a/docs/content/installation/metrics/metrics-configuration.md +++ b/docs/content/installation/metrics/metrics-configuration.md @@ -125,6 +125,6 @@ PostgreSQL Operator Monitoring infrastructure: | Name | Default | Required | Description | |------|---------|----------|-------------| | `pgo_image_prefix` | registry.developers.crunchydata.com/crunchydata | **Required** | Configures the image prefix used by the `pgo-deployer` container | -| `pgo_image_tag` | {{< param centosBase >}}-{{< param operatorVersion >}} | **Required** | Configures the image tag used by the `pgo-deployer` container | +| `pgo_image_tag` | {{< param ubiBase >}}-{{< param operatorVersion >}} | **Required** | Configures the image tag used by the `pgo-deployer` container | [k8s-service-type]: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types diff --git a/docs/content/pgo-client/common-tasks.md b/docs/content/pgo-client/common-tasks.md index 5bb672d6eb..27680bd97b 100644 --- a/docs/content/pgo-client/common-tasks.md +++ b/docs/content/pgo-client/common-tasks.md @@ -107,7 +107,7 @@ which yields output similar to: BasicAuth: "" Cluster: CCPImagePrefix: crunchydata - CCPImageTag: {{< param centosBase >}}-{{< param postgresVersion >}}-{{< param operatorVersion >}} + CCPImageTag: {{< param ubiBase >}}-{{< param postgresVersion >}}-{{< param operatorVersion >}} Policies: "" Metrics: false Badger: false @@ -134,7 +134,7 @@ Cluster: Pgo: Audit: false PGOImagePrefix: crunchydata - PGOImageTag: {{< param centosBase >}}-{{< param operatorVersion >}} + PGOImageTag: {{< param ubiBase >}}-{{< param operatorVersion >}} PrimaryStorage: nfsstorage BackupStorage: nfsstorage ReplicaStorage: nfsstorage @@ -251,7 +251,7 @@ example below, the cluster will use PostgreSQL {{< param postgresVersion >}} and ```shell pgo create cluster hagiscluster \ --ccp-image=crunchy-postgres-gis-ha \ - --ccp-image-tag={{< param centosBase >}}-{{< param postgresVersion >}}-{{< param postgisVersion >}}-{{< param operatorVersion >}} + --ccp-image-tag={{< param ubiBase >}}-{{< param postgresVersion >}}-{{< param postgisVersion >}}-{{< param operatorVersion >}} ``` #### Create a PostgreSQL Cluster with a Tablespace @@ -376,7 +376,7 @@ pgo show cluster hacluster which will yield output similar to: ``` -cluster : hacluster (crunchy-postgres-ha:{{< param centosBase >}}-{{< param postgresVersion >}}-{{< param operatorVersion >}}) +cluster : hacluster (crunchy-postgres-ha:{{< param ubiBase >}}-{{< param postgresVersion >}}-{{< param operatorVersion >}}) pod : hacluster-6dc6cfcfb9-f9knq (Running) on node01 (1/1) (primary) pvc : hacluster resources : CPU Limit= Memory Limit=, CPU Request= Memory Request= diff --git a/docs/content/releases/4.6.7.md b/docs/content/releases/4.6.7.md new file mode 100644 index 0000000000..a8dd0df08f --- /dev/null +++ b/docs/content/releases/4.6.7.md @@ -0,0 +1,17 @@ +--- +title: "4.6.7" +date: +draft: false +weight: 53 +--- + +Crunchy Data announces the release of PGO, the Postgres Operator 4.6.7. + +The PostgreSQL Operator is released in conjunction with the [Crunchy Container Suite](https://github.com/CrunchyData/crunchy-containers/). + +PostgreSQL Operator 4.6.7 release includes the following software versions upgrades: + +- [PostgreSQL](https://www.postgresql.org) versions 14.3, 13.7, 12.11, 11.16, and 10.21 are now available. +- [PostGIS](http://postgis.net/) version 3.1.4 is now available. +- The [pg_partman](https://github.com/pgpartman/pg_partman) extension is now at version 4.6.1. +- The [TimescaleDB](https://github.com/timescale/timescaledb) extension is now at version 2.6.1. diff --git a/docs/content/support/_index.md b/docs/content/support/_index.md index 0a2ca60346..f8a974c84b 100644 --- a/docs/content/support/_index.md +++ b/docs/content/support/_index.md @@ -13,7 +13,7 @@ There are a few options available for community support of the [PGO: the Postgre In all cases, please be sure to provide as many details as possible in regards to your issue, including: - Your Platform (e.g. Kubernetes vX.YY.Z) -- Operator Version (e.g. {{< param centosBase >}}-{{< param operatorVersion >}}) +- Operator Version (e.g. {{< param ubiBase >}}-{{< param operatorVersion >}}) - A detailed description of the issue, as well as steps you took that lead up to the issue - Any relevant logs - Any additional information you can provide that you may find helpful diff --git a/examples/create-by-resource/fromcrd.json b/examples/create-by-resource/fromcrd.json index f36bef16f0..77462bc30f 100644 --- a/examples/create-by-resource/fromcrd.json +++ b/examples/create-by-resource/fromcrd.json @@ -10,7 +10,7 @@ "deployment-name": "fromcrd", "name": "fromcrd", "pg-cluster": "fromcrd", - "pgo-version": "4.6.6", + "pgo-version": "4.6.7", "pgouser": "pgoadmin" }, "name": "fromcrd", @@ -45,7 +45,7 @@ "supplementalgroups": "" }, "ccpimage": "crunchy-postgres-ha", - "ccpimagetag": "centos8-13.6-4.6.6", + "ccpimagetag": "ubi8-13.7-4.6.7", "clustername": "fromcrd", "database": "userdb", "exporterport": "9187", @@ -60,7 +60,7 @@ "port": "5432", "user": "testuser", "userlabels": { - "pgo-version": "4.6.6" + "pgo-version": "4.6.7" } } } diff --git a/examples/envs.sh b/examples/envs.sh index 97903aa02c..58849805c3 100644 --- a/examples/envs.sh +++ b/examples/envs.sh @@ -19,8 +19,8 @@ export PGO_CONF_DIR=$PGOROOT/installers/ansible/roles/pgo-operator/files # the version of the Operator you run is set by these vars export PGO_IMAGE_PREFIX=registry.developers.crunchydata.com/crunchydata -export PGO_BASEOS=centos8 -export PGO_VERSION=4.6.6 +export PGO_BASEOS=ubi8 +export PGO_VERSION=4.6.7 export PGO_IMAGE_TAG=$PGO_BASEOS-$PGO_VERSION # for setting the pgo apiserver port, disabling TLS or not verifying TLS diff --git a/examples/helm/README.md b/examples/helm/README.md index 55c91e6de4..2d6dca35f1 100644 --- a/examples/helm/README.md +++ b/examples/helm/README.md @@ -64,7 +64,7 @@ The following values can also be set: - `ha`: Whether or not to deploy a high availability PostgreSQL cluster. Can be either `true` or `false`, defaults to `false`. - `imagePrefix`: The prefix of the container images to use for this PostgreSQL cluster. Default to `registry.developers.crunchydata.com/crunchydata`. - `image`: The name of the container image to use for the PostgreSQL cluster. Defaults to `crunchy-postgres-ha`. -- `imageTag`: The container image tag to use. Defaults to `centos8-13.6-4.6.6`. +- `imageTag`: The container image tag to use. Defaults to `ubi8-13.7-4.6.7`. - `memory`: The memory limit for the PostgreSQL cluster. Follows standard Kubernetes formatting. - `monitoring`: Whether or not to enable monitoring / metrics collection for this PostgreSQL instance. Can either be `true` or `false`, defaults to `false`. diff --git a/examples/helm/postgres/Chart.yaml b/examples/helm/postgres/Chart.yaml index 2062a21702..1c539ae345 100644 --- a/examples/helm/postgres/Chart.yaml +++ b/examples/helm/postgres/Chart.yaml @@ -20,4 +20,4 @@ version: 0.2.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. -appVersion: 4.6.6 +appVersion: 4.6.7 diff --git a/examples/helm/postgres/templates/pgcluster.yaml b/examples/helm/postgres/templates/pgcluster.yaml index d9514198f1..e5f465fad8 100644 --- a/examples/helm/postgres/templates/pgcluster.yaml +++ b/examples/helm/postgres/templates/pgcluster.yaml @@ -28,7 +28,7 @@ spec: storagetype: dynamic ccpimage: {{ .Values.image | default "crunchy-postgres-ha" | quote }} ccpimageprefix: {{ .Values.imagePrefix | default "registry.developers.crunchydata.com/crunchydata" | quote }} - ccpimagetag: {{ .Values.imageTag | default "centos8-13.6-4.6.6" | quote }} + ccpimagetag: {{ .Values.imageTag | default "ubi8-13.7-4.6.7" | quote }} clustername: {{ .Values.name | quote }} database: {{ .Values.name | quote }} {{- if .Values.monitoring }} diff --git a/examples/helm/postgres/values.yaml b/examples/helm/postgres/values.yaml index 0ab73e712b..5d401ad8d5 100644 --- a/examples/helm/postgres/values.yaml +++ b/examples/helm/postgres/values.yaml @@ -10,5 +10,5 @@ password: W4tch0ut4hippo$ # ha: true # imagePrefix: registry.developers.crunchydata.com/crunchydata # image: crunchy-postgres-ha -# imageTag: centos8-13.6-4.6.6 +# imageTag: ubi8-13.7-4.6.7 # memory: 1Gi diff --git a/examples/kustomize/createcluster/README.md b/examples/kustomize/createcluster/README.md index 51990cc1f7..415cb919df 100644 --- a/examples/kustomize/createcluster/README.md +++ b/examples/kustomize/createcluster/README.md @@ -44,13 +44,13 @@ pgo show cluster hippo -n pgo ``` You will see something like this if successful: ``` -cluster : hippo (crunchy-postgres-ha:centos8-13.6-4.6.6) +cluster : hippo (crunchy-postgres-ha:ubi8-13.7-4.6.7) pod : hippo-8fb6bd96-j87wq (Running) on gke-xxxx-default-pool-38e946bd-257w (1/1) (primary) pvc: hippo (1Gi) deployment : hippo deployment : hippo-backrest-shared-repo service : hippo - ClusterIP (10.0.56.86) - Ports (2022/TCP, 5432/TCP) - labels : pgo-version=4.6.6 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.7 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata ``` Feel free to run other pgo cli commands on the hippo cluster @@ -79,7 +79,7 @@ pgo show cluster dev-hippo -n pgo ``` You will see something like this if successful: ``` -cluster : dev-hippo (crunchy-postgres-ha:centos8-13.6-4.6.6) +cluster : dev-hippo (crunchy-postgres-ha:ubi8-13.7-4.6.7) pod : dev-hippo-588d4cb746-bwrxb (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: dev-hippo (1Gi) deployment : dev-hippo @@ -87,7 +87,7 @@ cluster : dev-hippo (crunchy-postgres-ha:centos8-13.6-4.6.6) deployment : dev-hippo-pgbouncer service : dev-hippo - ClusterIP (10.0.62.87) - Ports (2022/TCP, 5432/TCP) service : dev-hippo-pgbouncer - ClusterIP (10.0.48.120) - Ports (5432/TCP) - labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.6.6 pgouser=admin + labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.6.7 pgouser=admin ``` #### staging The staging overlay will deploy a crunchy postgreSQL cluster with 2 replica's with annotations added @@ -113,7 +113,7 @@ pgo show cluster staging-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : staging-hippo (crunchy-postgres-ha:centos8-13.6-4.6.6) +cluster : staging-hippo (crunchy-postgres-ha:ubi8-13.7-4.6.7) pod : staging-hippo-85cf6dcb65-9h748 (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: staging-hippo (1Gi) pod : staging-hippo-lnxw-cf47d8c8b-6r4wn (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (replica) @@ -128,7 +128,7 @@ cluster : staging-hippo (crunchy-postgres-ha:centos8-13.6-4.6.6) service : staging-hippo-replica - ClusterIP (10.0.56.57) - Ports (2022/TCP, 5432/TCP) pgreplica : staging-hippo-lnxw pgreplica : staging-hippo-rpl1 - labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.6.6 pgouser=admin vendor=crunchydata + labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.6.7 pgouser=admin vendor=crunchydata ``` #### production @@ -154,7 +154,7 @@ pgo show cluster prod-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : prod-hippo (crunchy-postgres-ha:centos8-13.6-4.6.6) +cluster : prod-hippo (crunchy-postgres-ha:ubi8-13.7-4.6.7) pod : prod-hippo-5d6dd46497-rr67c (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (primary) pvc: prod-hippo (1Gi) pod : prod-hippo-flty-84d97c8769-2pzbh (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (replica) @@ -165,7 +165,7 @@ cluster : prod-hippo (crunchy-postgres-ha:centos8-13.6-4.6.6) service : prod-hippo - ClusterIP (10.0.56.18) - Ports (2022/TCP, 5432/TCP) service : prod-hippo-replica - ClusterIP (10.0.56.101) - Ports (2022/TCP, 5432/TCP) pgreplica : prod-hippo-flty - labels : pgo-version=4.6.6 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.7 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata ``` ### Delete the clusters To delete the clusters run the following pgo cli commands diff --git a/examples/kustomize/createcluster/base/pgcluster.yaml b/examples/kustomize/createcluster/base/pgcluster.yaml index 331bff86bf..fa1d0f4758 100644 --- a/examples/kustomize/createcluster/base/pgcluster.yaml +++ b/examples/kustomize/createcluster/base/pgcluster.yaml @@ -10,7 +10,7 @@ metadata: deployment-name: hippo name: hippo pg-cluster: hippo - pgo-version: 4.6.6 + pgo-version: 4.6.7 pgouser: admin name: hippo namespace: pgo @@ -46,7 +46,7 @@ spec: postgres: {} ccpimage: crunchy-postgres-ha ccpimageprefix: registry.developers.crunchydata.com/crunchydata - ccpimagetag: centos8-13.6-4.6.6 + ccpimagetag: ubi8-13.7-4.6.7 clustername: hippo customconfig: "" database: hippo @@ -69,4 +69,4 @@ spec: port: "5432" user: hippo userlabels: - pgo-version: 4.6.6 + pgo-version: 4.6.7 diff --git a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml index 504976ddb5..394642f1a2 100644 --- a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml +++ b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml @@ -20,4 +20,4 @@ spec: storagetype: dynamic supplementalgroups: "" userlabels: - pgo-version: 4.6.6 + pgo-version: 4.6.7 diff --git a/installers/ansible/README.md b/installers/ansible/README.md index 1014b211ab..fb29d8b1af 100644 --- a/installers/ansible/README.md +++ b/installers/ansible/README.md @@ -4,7 +4,7 @@ PGO: The Postgres Operator from Crunchy Data

-Latest Release: 4.6.6 +Latest Release: 4.6.7 ## General diff --git a/installers/ansible/values.yaml b/installers/ansible/values.yaml index 2697315bb3..9a73352cf0 100644 --- a/installers/ansible/values.yaml +++ b/installers/ansible/values.yaml @@ -17,7 +17,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.6-4.6.6" +ccp_image_tag: "ubi8-13.7-4.6.7" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -49,14 +49,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.6" +pgo_client_version: "4.6.7" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos8-4.6.6" +pgo_image_tag: "ubi8-4.6.7" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/gcp-marketplace/Makefile b/installers/gcp-marketplace/Makefile index 7075f7a8a3..5f37b6b973 100644 --- a/installers/gcp-marketplace/Makefile +++ b/installers/gcp-marketplace/Makefile @@ -6,7 +6,7 @@ MARKETPLACE_TOOLS ?= gcr.io/cloud-marketplace-tools/k8s/dev:$(MARKETPLACE_VERSIO MARKETPLACE_VERSION ?= 0.9.4 KUBECONFIG ?= $(HOME)/.kube/config PARAMETERS ?= {} -PGO_VERSION ?= 4.6.6 +PGO_VERSION ?= 4.6.7 IMAGE_BUILD_ARGS = --build-arg MARKETPLACE_VERSION='$(MARKETPLACE_VERSION)' \ --build-arg PGO_VERSION='$(PGO_VERSION)' diff --git a/installers/gcp-marketplace/README.md b/installers/gcp-marketplace/README.md index 7299b0dce0..d02353a985 100644 --- a/installers/gcp-marketplace/README.md +++ b/installers/gcp-marketplace/README.md @@ -59,7 +59,7 @@ Google Cloud Marketplace. ```shell IMAGE_REPOSITORY=gcr.io/crunchydata-public/postgres-operator - export PGO_VERSION=4.6.6 + export PGO_VERSION=4.6.7 export INSTALLER_IMAGE=${IMAGE_REPOSITORY}/deployer:${PGO_VERSION} export OPERATOR_IMAGE=${IMAGE_REPOSITORY}:${PGO_VERSION} export OPERATOR_IMAGE_API=${IMAGE_REPOSITORY}/pgo-apiserver:${PGO_VERSION} diff --git a/installers/gcp-marketplace/values.yaml b/installers/gcp-marketplace/values.yaml index 1e48c44118..d54ad31524 100644 --- a/installers/gcp-marketplace/values.yaml +++ b/installers/gcp-marketplace/values.yaml @@ -10,7 +10,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.6-4.6.6" +ccp_image_tag: "ubi8-13.7-4.6.7" create_rbac: "true" db_name: "" db_password_age_days: "0" @@ -32,9 +32,9 @@ pgo_admin_role_name: "pgoadmin" pgo_admin_username: "admin" pgo_client_container_install: "false" pgo_client_install: 'false' -pgo_client_version: "4.6.6" +pgo_client_version: "4.6.7" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.6" +pgo_image_tag: "ubi8-4.6.7" pgo_installation_name: '${OPERATOR_NAME}' pgo_operator_namespace: '${OPERATOR_NAMESPACE}' scheduler_timeout: "3600" diff --git a/installers/helm/Chart.yaml b/installers/helm/Chart.yaml index a325335768..cecab8c0ee 100644 --- a/installers/helm/Chart.yaml +++ b/installers/helm/Chart.yaml @@ -3,7 +3,7 @@ name: postgres-operator description: 'PGO: The Postgres Operator from Crunchy Data Helm Chart for Kubernetes' type: application version: 0.2.0 -appVersion: 4.6.6 +appVersion: 4.6.7 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/docs/static/logos/pgo.svg keywords: diff --git a/installers/helm/values.yaml b/installers/helm/values.yaml index 15c264cd32..ceca95bae3 100644 --- a/installers/helm/values.yaml +++ b/installers/helm/values.yaml @@ -37,7 +37,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "centos8-13.6-4.6.6" +ccp_image_tag: "ubi8-13.7-4.6.7" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -69,14 +69,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.6" +pgo_client_version: "4.6.7" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "centos8-4.6.6" +pgo_image_tag: "ubi8-4.6.7" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/kubectl/client-setup.sh b/installers/kubectl/client-setup.sh index f26ea0ff61..ca4e19ebf0 100755 --- a/installers/kubectl/client-setup.sh +++ b/installers/kubectl/client-setup.sh @@ -14,7 +14,7 @@ # This script should be run after the operator has been deployed PGO_OPERATOR_NAMESPACE="${PGO_OPERATOR_NAMESPACE:-pgo}" PGO_USER_ADMIN="${PGO_USER_ADMIN:-pgouser-admin}" -PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.6}" +PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.7}" PGO_CLIENT_URL="https://github.com/CrunchyData/postgres-operator/releases/download/${PGO_CLIENT_VERSION}" PGO_CMD="${PGO_CMD-kubectl}" diff --git a/installers/kubectl/postgres-operator-ocp311.yml b/installers/kubectl/postgres-operator-ocp311.yml index 47909c6eec..6d5af0b409 100644 --- a/installers/kubectl/postgres-operator-ocp311.yml +++ b/installers/kubectl/postgres-operator-ocp311.yml @@ -50,7 +50,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos8-13.6-4.6.6" + ccp_image_tag: "ubi8-13.7-4.6.7" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -83,14 +83,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.6" + pgo_client_version: "4.6.7" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos8-4.6.6" + pgo_image_tag: "ubi8-4.6.7" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -171,7 +171,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.6 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.7 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/kubectl/postgres-operator.yml b/installers/kubectl/postgres-operator.yml index f1956fde54..a93c9029c2 100644 --- a/installers/kubectl/postgres-operator.yml +++ b/installers/kubectl/postgres-operator.yml @@ -145,7 +145,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "centos8-13.6-4.6.6" + ccp_image_tag: "ubi8-13.7-4.6.7" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -178,14 +178,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.6" + pgo_client_version: "4.6.7" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "centos8-4.6.6" + pgo_image_tag: "ubi8-4.6.7" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -281,7 +281,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.6 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.7 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/ansible/README.md b/installers/metrics/ansible/README.md index ccbaf5f625..68748233c8 100644 --- a/installers/metrics/ansible/README.md +++ b/installers/metrics/ansible/README.md @@ -4,7 +4,7 @@ Crunchy Data

-Latest Release: 4.6.6 +Latest Release: 4.6.7 ## General diff --git a/installers/metrics/helm/Chart.yaml b/installers/metrics/helm/Chart.yaml index 8dc45f4d75..bb85550a43 100644 --- a/installers/metrics/helm/Chart.yaml +++ b/installers/metrics/helm/Chart.yaml @@ -3,6 +3,6 @@ name: postgres-operator-monitoring description: Install for Crunchy PostgreSQL Operator Monitoring type: application version: 0.2.0 -appVersion: 4.6.6 +appVersion: 4.6.7 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/docs/static/logos/pgo.svg diff --git a/installers/metrics/helm/helm_template.yaml b/installers/metrics/helm/helm_template.yaml index 5d24bbd5fe..a55e99389b 100644 --- a/installers/metrics/helm/helm_template.yaml +++ b/installers/metrics/helm/helm_template.yaml @@ -20,5 +20,5 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.6" +pgo_image_tag: "ubi8-4.6.7" diff --git a/installers/metrics/helm/values.yaml b/installers/metrics/helm/values.yaml index c7e6017df7..7759d36ab9 100644 --- a/installers/metrics/helm/values.yaml +++ b/installers/metrics/helm/values.yaml @@ -20,7 +20,7 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "centos8-4.6.6" +pgo_image_tag: "ubi8-4.6.7" # ===================== # Configuration Options diff --git a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml index fcd93e723c..3a4fbd0e1f 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml @@ -101,7 +101,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.6 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.7 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/kubectl/postgres-operator-metrics.yml b/installers/metrics/kubectl/postgres-operator-metrics.yml index 18fbfb7b9b..c1f2af57a1 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics.yml @@ -171,7 +171,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:centos8-4.6.6 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.7 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/olm/Makefile b/installers/olm/Makefile index 9d57a1333f..ca4dc1f166 100644 --- a/installers/olm/Makefile +++ b/installers/olm/Makefile @@ -2,16 +2,16 @@ .SUFFIXES: CCP_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -CCP_PG_FULLVERSION ?= 13.6 +CCP_PG_FULLVERSION ?= 13.7 CCP_POSTGIS_VERSION ?= 3.0 CONTAINER ?= docker KUBECONFIG ?= $(HOME)/.kube/config OLM_SDK_VERSION ?= 0.15.1 OLM_TOOLS ?= registry.localhost:5000/postgres-operator-olm-tools:$(OLM_SDK_VERSION) OLM_VERSION ?= 0.15.1 -PGO_BASEOS ?= centos8 +PGO_BASEOS ?= ubi8 PGO_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -PGO_VERSION ?= 4.6.6 +PGO_VERSION ?= 4.6.7 PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) CCP_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(PGO_VERSION) CCP_POSTGIS_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(CCP_POSTGIS_VERSION)-$(PGO_VERSION) diff --git a/installers/olm/description.openshift.md b/installers/olm/description.openshift.md index 6290d5545c..5ceeac8b03 100644 --- a/installers/olm/description.openshift.md +++ b/installers/olm/description.openshift.md @@ -188,7 +188,7 @@ spec: annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: ${cluster_image_prefix} - ccpimagetag: centos8-13.6-${PGO_VERSION} + ccpimagetag: ubi8-13.7-${PGO_VERSION} clustername: ${pgo_cluster_name} database: ${pgo_cluster_name} exporterport: "9187" diff --git a/installers/olm/description.upstream.md b/installers/olm/description.upstream.md index d907711b81..c22eae4918 100644 --- a/installers/olm/description.upstream.md +++ b/installers/olm/description.upstream.md @@ -168,7 +168,7 @@ spec: annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: ${cluster_image_prefix} - ccpimagetag: centos8-13.6-${PGO_VERSION} + ccpimagetag: ubi8-13.7-${PGO_VERSION} clustername: ${pgo_cluster_name} database: ${pgo_cluster_name} exporterport: "9187" diff --git a/internal/util/util_test.go b/internal/util/util_test.go index 08c6966a3a..283c88b916 100644 --- a/internal/util/util_test.go +++ b/internal/util/util_test.go @@ -17,50 +17,50 @@ func TestGetStandardImageTag(t *testing.T) { expected string }{ { - "image: crunchy-postgres-ha, tag: centos8-12.4-4.5.0", + "image: crunchy-postgres-ha, tag: ubi8-12.4-4.5.0", "crunchy-postgres-ha", - "centos8-12.4-4.5.0", - "centos8-12.4-4.5.0", + "ubi8-12.4-4.5.0", + "ubi8-12.4-4.5.0", }, { - "image: crunchy-postgres-gis-ha, tag: centos8-12.4-3.0-4.5.0", + "image: crunchy-postgres-gis-ha, tag: ubi8-12.4-3.0-4.5.0", "crunchy-postgres-gis-ha", - "centos8-12.4-3.0-4.5.0", - "centos8-12.4-4.5.0", + "ubi8-12.4-3.0-4.5.0", + "ubi8-12.4-4.5.0", }, { - "image: crunchy-postgres-ha, tag: centos8-12.4-4.5.0-beta.1", + "image: crunchy-postgres-ha, tag: ubi8-12.4-4.5.0-beta.1", "crunchy-postgres-ha", - "centos8-12.4-4.5.0-beta.1", - "centos8-12.4-4.5.0-beta.1", + "ubi8-12.4-4.5.0-beta.1", + "ubi8-12.4-4.5.0-beta.1", }, { - "image: crunchy-postgres-gis-ha, tag: centos8-12.4-3.0-4.5.0-beta.2", + "image: crunchy-postgres-gis-ha, tag: ubi8-12.4-3.0-4.5.0-beta.2", "crunchy-postgres-gis-ha", - "centos8-12.4-3.0-4.5.0-beta.2", - "centos8-12.4-4.5.0-beta.2", + "ubi8-12.4-3.0-4.5.0-beta.2", + "ubi8-12.4-4.5.0-beta.2", }, { - "image: crunchy-postgres-ha, tag: centos8-9.5.23-4.5.0-rc.1", + "image: crunchy-postgres-ha, tag: ubi8-9.5.23-4.5.0-rc.1", "crunchy-postgres-ha", - "centos8-9.5.23-4.5.0-rc.1", - "centos8-9.5.23-4.5.0-rc.1", + "ubi8-9.5.23-4.5.0-rc.1", + "ubi8-9.5.23-4.5.0-rc.1", }, { - "image: crunchy-postgres-gis-ha, tag: centos8-9.5.23-2.4-4.5.0-rc.1", + "image: crunchy-postgres-gis-ha, tag: ubi8-9.5.23-2.4-4.5.0-rc.1", "crunchy-postgres-gis-ha", - "centos8-9.5.23-2.4-4.5.0-rc.1", - "centos8-9.5.23-4.5.0-rc.1", + "ubi8-9.5.23-2.4-4.5.0-rc.1", + "ubi8-9.5.23-4.5.0-rc.1", }, { - "image: crunchy-postgres-gis-ha, tag: centos8-13.0-3.0-4.5.0-rc.1", + "image: crunchy-postgres-gis-ha, tag: ubi8-13.0-3.0-4.5.0-rc.1", "crunchy-postgres-gis-ha", - "centos8-13.0-3.0-4.5.0-rc.1", - "centos8-13.0-4.5.0-rc.1", + "ubi8-13.0-3.0-4.5.0-rc.1", + "ubi8-13.0-4.5.0-rc.1", }, { - "image: crunchy-postgres-gis-ha, tag: centos8-custom123", + "image: crunchy-postgres-gis-ha, tag: ubi8-custom123", "crunchy-postgres-gis-ha", - "centos8-custom123", - "centos8-custom123", + "ubi8-custom123", + "ubi8-custom123", }, { - "image: crunchy-postgres-gis-ha, tag: centos8-custom123-moreinfo-789", + "image: crunchy-postgres-gis-ha, tag: ubi8-custom123-moreinfo-789", "crunchy-postgres-gis-ha", - "centos8-custom123-moreinfo-789", - "centos8-custom123-moreinfo-789", + "ubi8-custom123-moreinfo-789", + "ubi8-custom123-moreinfo-789", }, } diff --git a/pkg/apis/crunchydata.com/v1/doc.go b/pkg/apis/crunchydata.com/v1/doc.go index 9fa7361e77..a376ec5a77 100644 --- a/pkg/apis/crunchydata.com/v1/doc.go +++ b/pkg/apis/crunchydata.com/v1/doc.go @@ -53,7 +53,7 @@ cluster. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.6", + '{"ClientVersion":"4.6.7", "Namespace":"pgouser1", "Name":"mycluster", $PGO_APISERVER_URL/clusters @@ -72,7 +72,7 @@ show all of the clusters that are in the given namespace. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.6", + '{"ClientVersion":"4.6.7", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/showclusters @@ -82,7 +82,7 @@ $PGO_APISERVER_URL/showclusters curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.6", + '{"ClientVersion":"4.6.7", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/clustersdelete @@ -90,7 +90,7 @@ $PGO_APISERVER_URL/clustersdelete Schemes: http, https BasePath: / - Version: 4.6.6 + Version: 4.6.7 License: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0 Contact: Crunchy Data https://www.crunchydata.com/ diff --git a/pkg/apiservermsgs/common.go b/pkg/apiservermsgs/common.go index c65c29685d..f9111ee8c3 100644 --- a/pkg/apiservermsgs/common.go +++ b/pkg/apiservermsgs/common.go @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -const PGO_VERSION = "4.6.6" +const PGO_VERSION = "4.6.7" // Ok status const Ok = "ok" diff --git a/redhat/atomic/help.1 b/redhat/atomic/help.1 index 3d693281d3..781a2502f4 100644 --- a/redhat/atomic/help.1 +++ b/redhat/atomic/help.1 @@ -56,4 +56,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa \fB\fCRelease=\fR .PP -The specific release number of the container. For example, Release="4.6.6" +The specific release number of the container. For example, Release="4.6.7" diff --git a/redhat/atomic/help.md b/redhat/atomic/help.md index 247f64c7d3..39be30fb22 100644 --- a/redhat/atomic/help.md +++ b/redhat/atomic/help.md @@ -45,4 +45,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa `Release=` -The specific release number of the container. For example, Release="4.6.6" +The specific release number of the container. For example, Release="4.6.7" From 724c8d192da3648a3a67177bb5a522150b506444 Mon Sep 17 00:00:00 2001 From: ValClarkson Date: Wed, 1 Jun 2022 16:25:12 -0400 Subject: [PATCH 257/276] updated ansible version in makefile and dockerfile [14721] --- Makefile | 2 +- build/pgo-deployer/Dockerfile | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 8f4a2373d7..5eaf11fcd5 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Default values if not already set -ANSIBLE_VERSION ?= 2.9.* +ANSIBLE_VERSION ?= 2.12.* PGOROOT ?= $(CURDIR) PGO_BASEOS ?= ubi8 PGO_IMAGE_PREFIX ?= crunchydata diff --git a/build/pgo-deployer/Dockerfile b/build/pgo-deployer/Dockerfile index 240ec8a440..d25bd75a79 100644 --- a/build/pgo-deployer/Dockerfile +++ b/build/pgo-deployer/Dockerfile @@ -19,7 +19,7 @@ RUN if [ "$DFSET" = "centos" ] ; then \ && ${PACKAGER} -y install \ --setopt=skip_missing_names_on_install=False \ kubectl \ - ansible-${ANSIBLE_VERSION} \ + ansible-core-${ANSIBLE_VERSION} \ which \ gettext \ && ${PACKAGER} -y clean all ; \ @@ -32,7 +32,7 @@ RUN if [ "$BASEOS" = "rhel7" ] ; then \ --setopt=skip_missing_names_on_install=False \ --enablerepo='rhel-7-server-ose-4.4-rpms' \ openshift-clients \ - ansible-${ANSIBLE_VERSION} \ + ansible-core-${ANSIBLE_VERSION} \ which \ gettext \ && ${PACKAGER} -y clean all --enablerepo='rhel-7-server-ose-4.4-rpms' ; \ @@ -45,7 +45,7 @@ RUN if [ "$BASEOS" = "ubi7" ] ; then \ --setopt=skip_missing_names_on_install=False \ --enablerepo='rhel-7-server-ose-4.4-rpms' \ openshift-clients \ - ansible-${ANSIBLE_VERSION} \ + ansible-core-${ANSIBLE_VERSION} \ which \ gettext \ && ${PACKAGER} -y clean all --enablerepo='rhel-7-server-ose-4.4-rpms' ; \ @@ -58,7 +58,7 @@ RUN if [ "$BASEOS" = "ubi8" ] ; then \ --setopt=skip_missing_names_on_install=False \ --enablerepo='rhocp-4.5-for-rhel-8-x86_64-rpms' \ openshift-clients \ - ansible-${ANSIBLE_VERSION} \ + ansible-core-${ANSIBLE_VERSION} \ which \ gettext \ && ${PACKAGER} -y clean all --enablerepo='rhocp-4.5-for-rhel-8-x86_64-rpms' ; \ From f67d580b1c81909808ef5b102df5181e103f259a Mon Sep 17 00:00:00 2001 From: Val Date: Thu, 2 Jun 2022 12:30:57 -0400 Subject: [PATCH 258/276] removed ansible pinned version, will now install latest ansible (#3242) [sc-14721] --- Makefile | 2 -- build/pgo-deployer/Dockerfile | 9 ++++----- installers/image/conf/kubernetes.repo | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 5eaf11fcd5..8b8eed62b5 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,5 @@ # Default values if not already set -ANSIBLE_VERSION ?= 2.12.* PGOROOT ?= $(CURDIR) PGO_BASEOS ?= ubi8 PGO_IMAGE_PREFIX ?= crunchydata @@ -161,7 +160,6 @@ $(PGOROOT)/build/%/Dockerfile: --build-arg PREFIX=$(PGO_IMAGE_PREFIX) \ --build-arg PGVERSION=$(PGO_PG_VERSION) \ --build-arg BACKREST_VERSION=$(PGO_BACKREST_VERSION) \ - --build-arg ANSIBLE_VERSION=$(ANSIBLE_VERSION) \ --build-arg DFSET=$(DFSET) \ --build-arg PACKAGER=$(PACKAGER) \ $(PGOROOT) diff --git a/build/pgo-deployer/Dockerfile b/build/pgo-deployer/Dockerfile index d25bd75a79..e57652ab85 100644 --- a/build/pgo-deployer/Dockerfile +++ b/build/pgo-deployer/Dockerfile @@ -4,7 +4,6 @@ ARG PREFIX FROM ${PREFIX}/pgo-base:${BASEOS}-${BASEVER} ARG BASEOS -ARG ANSIBLE_VERSION ARG PACKAGER ARG DFSET @@ -19,7 +18,7 @@ RUN if [ "$DFSET" = "centos" ] ; then \ && ${PACKAGER} -y install \ --setopt=skip_missing_names_on_install=False \ kubectl \ - ansible-core-${ANSIBLE_VERSION} \ + ansible \ which \ gettext \ && ${PACKAGER} -y clean all ; \ @@ -32,7 +31,7 @@ RUN if [ "$BASEOS" = "rhel7" ] ; then \ --setopt=skip_missing_names_on_install=False \ --enablerepo='rhel-7-server-ose-4.4-rpms' \ openshift-clients \ - ansible-core-${ANSIBLE_VERSION} \ + ansible \ which \ gettext \ && ${PACKAGER} -y clean all --enablerepo='rhel-7-server-ose-4.4-rpms' ; \ @@ -45,7 +44,7 @@ RUN if [ "$BASEOS" = "ubi7" ] ; then \ --setopt=skip_missing_names_on_install=False \ --enablerepo='rhel-7-server-ose-4.4-rpms' \ openshift-clients \ - ansible-core-${ANSIBLE_VERSION} \ + ansible \ which \ gettext \ && ${PACKAGER} -y clean all --enablerepo='rhel-7-server-ose-4.4-rpms' ; \ @@ -58,7 +57,7 @@ RUN if [ "$BASEOS" = "ubi8" ] ; then \ --setopt=skip_missing_names_on_install=False \ --enablerepo='rhocp-4.5-for-rhel-8-x86_64-rpms' \ openshift-clients \ - ansible-core-${ANSIBLE_VERSION} \ + ansible \ which \ gettext \ && ${PACKAGER} -y clean all --enablerepo='rhocp-4.5-for-rhel-8-x86_64-rpms' ; \ diff --git a/installers/image/conf/kubernetes.repo b/installers/image/conf/kubernetes.repo index 0a8b4cf2bf..8830e53746 100644 --- a/installers/image/conf/kubernetes.repo +++ b/installers/image/conf/kubernetes.repo @@ -3,5 +3,5 @@ name=Kubernetes baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64 enabled=1 gpgcheck=1 -repo_gpgcheck=1 +repo_gpgcheck=0 gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg \ No newline at end of file From d9a7e18b874ea7a4fe17759905255ddb050c7f91 Mon Sep 17 00:00:00 2001 From: Andrew L'Ecuyer Date: Wed, 8 Jun 2022 18:15:17 +0000 Subject: [PATCH 259/276] Fix Ansible Dependency & Update Aync Directory Adds the "python38-jmespath" package to the "pgo-deployer" image for UBI 8 as required by Ansible, while also updating the Ansible async directory for proper async functionality in all "pgo-deployer" image builds. Issue: [sc-14754] --- build/pgo-deployer/Dockerfile | 1 + installers/ansible/roles/pgo-operator/tasks/main.yml | 2 ++ installers/metrics/ansible/roles/pgo-metrics/tasks/main.yml | 2 ++ 3 files changed, 5 insertions(+) diff --git a/build/pgo-deployer/Dockerfile b/build/pgo-deployer/Dockerfile index e57652ab85..711ef41fb4 100644 --- a/build/pgo-deployer/Dockerfile +++ b/build/pgo-deployer/Dockerfile @@ -60,6 +60,7 @@ RUN if [ "$BASEOS" = "ubi8" ] ; then \ ansible \ which \ gettext \ + python38-jmespath \ && ${PACKAGER} -y clean all --enablerepo='rhocp-4.5-for-rhel-8-x86_64-rpms' ; \ fi diff --git a/installers/ansible/roles/pgo-operator/tasks/main.yml b/installers/ansible/roles/pgo-operator/tasks/main.yml index 205c7385ed..aafe7b6679 100644 --- a/installers/ansible/roles/pgo-operator/tasks/main.yml +++ b/installers/ansible/roles/pgo-operator/tasks/main.yml @@ -340,6 +340,8 @@ - name: Wait for PGO to finish deploying command: "{{ kubectl_or_oc }} rollout status deployment/postgres-operator -n {{ pgo_operator_namespace }}" async: 600 + vars: + ansible_async_dir: /tmp/.ansible_async - name: PGO Client tags: diff --git a/installers/metrics/ansible/roles/pgo-metrics/tasks/main.yml b/installers/metrics/ansible/roles/pgo-metrics/tasks/main.yml index 994bc6e9bd..8c781a0b2b 100644 --- a/installers/metrics/ansible/roles/pgo-metrics/tasks/main.yml +++ b/installers/metrics/ansible/roles/pgo-metrics/tasks/main.yml @@ -137,6 +137,8 @@ poll: 0 loop: "{{ deployments }}" register: deployment_results + vars: + ansible_async_dir: /tmp/.ansible_async - name: Check Metrics Deployment Status async_status: From 653a2f97dc173e01e81adbe4a9e892d0c511e6ba Mon Sep 17 00:00:00 2001 From: Joseph Mckulka <16840147+jmckulk@users.noreply.github.com> Date: Thu, 4 Aug 2022 14:03:30 -0400 Subject: [PATCH 260/276] Bump 4.6.7 to 4.6.8 (#3337) --- Makefile | 4 ++-- README.md | 2 +- bin/push-ccp-to-gcr.sh | 2 +- conf/postgres-operator/pgo.yaml | 4 ++-- docs/config.toml | 12 ++++++------ docs/content/Configuration/compatibility.md | 5 +++++ docs/content/releases/4.6.8.md | 17 +++++++++++++++++ installers/ansible/README.md | 2 +- installers/ansible/values.yaml | 6 +++--- installers/gcp-marketplace/Makefile | 2 +- installers/gcp-marketplace/README.md | 2 +- installers/gcp-marketplace/values.yaml | 6 +++--- installers/helm/Chart.yaml | 2 +- installers/helm/values.yaml | 6 +++--- installers/kubectl/client-setup.sh | 2 +- installers/kubectl/postgres-operator-ocp311.yml | 8 ++++---- installers/kubectl/postgres-operator.yml | 8 ++++---- installers/metrics/ansible/README.md | 2 +- installers/metrics/helm/Chart.yaml | 2 +- installers/metrics/helm/helm_template.yaml | 2 +- installers/metrics/helm/values.yaml | 2 +- .../postgres-operator-metrics-ocp311.yml | 2 +- .../kubectl/postgres-operator-metrics.yml | 2 +- installers/olm/Makefile | 4 ++-- installers/olm/description.openshift.md | 2 +- installers/olm/description.upstream.md | 2 +- pkg/apis/crunchydata.com/v1/doc.go | 8 ++++---- pkg/apiservermsgs/common.go | 2 +- redhat/atomic/help.1 | 2 +- redhat/atomic/help.md | 2 +- 30 files changed, 73 insertions(+), 51 deletions(-) create mode 100644 docs/content/releases/4.6.8.md diff --git a/Makefile b/Makefile index 8b8eed62b5..9c88aec67e 100644 --- a/Makefile +++ b/Makefile @@ -4,9 +4,9 @@ PGOROOT ?= $(CURDIR) PGO_BASEOS ?= ubi8 PGO_IMAGE_PREFIX ?= crunchydata PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) -PGO_VERSION ?= 4.6.7 +PGO_VERSION ?= 4.6.8 PGO_PG_VERSION ?= 13 -PGO_PG_FULLVERSION ?= 13.7 +PGO_PG_FULLVERSION ?= 13.8 PGO_BACKREST_VERSION ?= 2.31 PACKAGER ?= yum diff --git a/README.md b/README.md index 8510ec3f01..7ee8f3148f 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ to start as quickly as: ```shell kubectl create namespace pgo -kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.7/installers/kubectl/postgres-operator.yml +kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.8/installers/kubectl/postgres-operator.yml ``` Otherwise, we highly recommend following the instructions from our [Quickstart](https://access.crunchydata.com/documentation/postgres-operator/latest/quickstart/). diff --git a/bin/push-ccp-to-gcr.sh b/bin/push-ccp-to-gcr.sh index 0b7141409e..3b6447e745 100755 --- a/bin/push-ccp-to-gcr.sh +++ b/bin/push-ccp-to-gcr.sh @@ -16,7 +16,7 @@ GCR_IMAGE_PREFIX=gcr.io/crunchy-dev-test CCP_IMAGE_PREFIX=crunchydata -CCP_IMAGE_TAG=ubi8-13.7-4.6.7 +CCP_IMAGE_TAG=ubi8-13.8-4.6.8 IMAGES=( crunchy-prometheus diff --git a/conf/postgres-operator/pgo.yaml b/conf/postgres-operator/pgo.yaml index a833f68721..bc44937057 100644 --- a/conf/postgres-operator/pgo.yaml +++ b/conf/postgres-operator/pgo.yaml @@ -2,7 +2,7 @@ Cluster: CCPImagePrefix: registry.developers.crunchydata.com/crunchydata Metrics: false Badger: false - CCPImageTag: ubi8-13.7-4.6.7 + CCPImageTag: ubi8-13.8-4.6.8 Port: 5432 PGBadgerPort: 10000 ExporterPort: 9187 @@ -80,4 +80,4 @@ Storage: Pgo: Audit: false PGOImagePrefix: registry.developers.crunchydata.com/crunchydata - PGOImageTag: ubi8-4.6.7 + PGOImageTag: ubi8-4.6.8 diff --git a/docs/config.toml b/docs/config.toml index 8175bc86c1..b98e64df3f 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -25,12 +25,12 @@ disableNavChevron = false # set true to hide next/prev chevron, default is false highlightClientSide = false # set true to use highlight.pack.js instead of the default hugo chroma highlighter menushortcutsnewtab = true # set true to open shortcuts links to a new tab/window enableGitInfo = true -operatorVersion = "4.6.7" -postgresVersion = "13.7" -postgresVersion13 = "13.7" -postgresVersion12 = "12.11" -postgresVersion11 = "11.16" -postgresVersion10 = "10.21" +operatorVersion = "4.6.8" +postgresVersion = "13.8" +postgresVersion13 = "13.8" +postgresVersion12 = "12.12" +postgresVersion11 = "11.17" +postgresVersion10 = "10.22" postgisVersion = "3.0" ubiBase = "ubi8" diff --git a/docs/content/Configuration/compatibility.md b/docs/content/Configuration/compatibility.md index 3c21881ba4..0f87f58c9b 100644 --- a/docs/content/Configuration/compatibility.md +++ b/docs/content/Configuration/compatibility.md @@ -12,6 +12,11 @@ version dependencies between the two projects. Below are the operator releases a | Operator Release | Container Release | Postgres | PgBackrest Version |:----------|:-------------|:------------|:-------------- +| 4.6.8 | 4.6.8 | 13.8 | 2.31 | +|||12.12|2.31| +|||11.17|2.31| +|||10.22|2.31| +|||| | 4.6.7 | 4.6.7 | 13.7 | 2.31 | |||12.11|2.31| |||11.16|2.31| diff --git a/docs/content/releases/4.6.8.md b/docs/content/releases/4.6.8.md new file mode 100644 index 0000000000..f4fa235b88 --- /dev/null +++ b/docs/content/releases/4.6.8.md @@ -0,0 +1,17 @@ +--- +title: "4.6.8" +date: +draft: false +weight: 52 +--- + +Crunchy Data announces the release of PGO, the Postgres Operator 4.6.8. + +The PostgreSQL Operator is released in conjunction with the [Crunchy Container Suite](https://github.com/CrunchyData/crunchy-containers/). + +PostgreSQL Operator 4.6.8 release includes the following software versions upgrades: + +- [PostgreSQL](https://www.postgresql.org) versions 13.8, 12.12, 11.17, and 10.22 are now available. +- [PgBouncer](https://www.pgbouncer.org/) is now at version 1.17. +- The [pg_partman](https://github.com/pgpartman/pg_partman) extension is now at version 4.6.0. +- The [TimescaleDB](https://github.com/timescale/timescaledb) extension is now at version 2.7.1. diff --git a/installers/ansible/README.md b/installers/ansible/README.md index fb29d8b1af..283de422e2 100644 --- a/installers/ansible/README.md +++ b/installers/ansible/README.md @@ -4,7 +4,7 @@ PGO: The Postgres Operator from Crunchy Data

-Latest Release: 4.6.7 +Latest Release: 4.6.8 ## General diff --git a/installers/ansible/values.yaml b/installers/ansible/values.yaml index 9a73352cf0..08d39410c4 100644 --- a/installers/ansible/values.yaml +++ b/installers/ansible/values.yaml @@ -17,7 +17,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "ubi8-13.7-4.6.7" +ccp_image_tag: "ubi8-13.8-4.6.8" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -49,14 +49,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.7" +pgo_client_version: "4.6.8" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "ubi8-4.6.7" +pgo_image_tag: "ubi8-4.6.8" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/gcp-marketplace/Makefile b/installers/gcp-marketplace/Makefile index 5f37b6b973..0b8fef0a29 100644 --- a/installers/gcp-marketplace/Makefile +++ b/installers/gcp-marketplace/Makefile @@ -6,7 +6,7 @@ MARKETPLACE_TOOLS ?= gcr.io/cloud-marketplace-tools/k8s/dev:$(MARKETPLACE_VERSIO MARKETPLACE_VERSION ?= 0.9.4 KUBECONFIG ?= $(HOME)/.kube/config PARAMETERS ?= {} -PGO_VERSION ?= 4.6.7 +PGO_VERSION ?= 4.6.8 IMAGE_BUILD_ARGS = --build-arg MARKETPLACE_VERSION='$(MARKETPLACE_VERSION)' \ --build-arg PGO_VERSION='$(PGO_VERSION)' diff --git a/installers/gcp-marketplace/README.md b/installers/gcp-marketplace/README.md index d02353a985..c2c926ea0b 100644 --- a/installers/gcp-marketplace/README.md +++ b/installers/gcp-marketplace/README.md @@ -59,7 +59,7 @@ Google Cloud Marketplace. ```shell IMAGE_REPOSITORY=gcr.io/crunchydata-public/postgres-operator - export PGO_VERSION=4.6.7 + export PGO_VERSION=4.6.8 export INSTALLER_IMAGE=${IMAGE_REPOSITORY}/deployer:${PGO_VERSION} export OPERATOR_IMAGE=${IMAGE_REPOSITORY}:${PGO_VERSION} export OPERATOR_IMAGE_API=${IMAGE_REPOSITORY}/pgo-apiserver:${PGO_VERSION} diff --git a/installers/gcp-marketplace/values.yaml b/installers/gcp-marketplace/values.yaml index d54ad31524..e3a359919e 100644 --- a/installers/gcp-marketplace/values.yaml +++ b/installers/gcp-marketplace/values.yaml @@ -10,7 +10,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "ubi8-13.7-4.6.7" +ccp_image_tag: "ubi8-13.8-4.6.8" create_rbac: "true" db_name: "" db_password_age_days: "0" @@ -32,9 +32,9 @@ pgo_admin_role_name: "pgoadmin" pgo_admin_username: "admin" pgo_client_container_install: "false" pgo_client_install: 'false' -pgo_client_version: "4.6.7" +pgo_client_version: "4.6.8" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "ubi8-4.6.7" +pgo_image_tag: "ubi8-4.6.8" pgo_installation_name: '${OPERATOR_NAME}' pgo_operator_namespace: '${OPERATOR_NAMESPACE}' scheduler_timeout: "3600" diff --git a/installers/helm/Chart.yaml b/installers/helm/Chart.yaml index cecab8c0ee..c34e5cf578 100644 --- a/installers/helm/Chart.yaml +++ b/installers/helm/Chart.yaml @@ -3,7 +3,7 @@ name: postgres-operator description: 'PGO: The Postgres Operator from Crunchy Data Helm Chart for Kubernetes' type: application version: 0.2.0 -appVersion: 4.6.7 +appVersion: 4.6.8 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/docs/static/logos/pgo.svg keywords: diff --git a/installers/helm/values.yaml b/installers/helm/values.yaml index ceca95bae3..4be0d09151 100644 --- a/installers/helm/values.yaml +++ b/installers/helm/values.yaml @@ -37,7 +37,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "ubi8-13.7-4.6.7" +ccp_image_tag: "ubi8-13.8-4.6.8" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -69,14 +69,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.7" +pgo_client_version: "4.6.8" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "ubi8-4.6.7" +pgo_image_tag: "ubi8-4.6.8" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/kubectl/client-setup.sh b/installers/kubectl/client-setup.sh index ca4e19ebf0..8140ec366d 100755 --- a/installers/kubectl/client-setup.sh +++ b/installers/kubectl/client-setup.sh @@ -14,7 +14,7 @@ # This script should be run after the operator has been deployed PGO_OPERATOR_NAMESPACE="${PGO_OPERATOR_NAMESPACE:-pgo}" PGO_USER_ADMIN="${PGO_USER_ADMIN:-pgouser-admin}" -PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.7}" +PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.8}" PGO_CLIENT_URL="https://github.com/CrunchyData/postgres-operator/releases/download/${PGO_CLIENT_VERSION}" PGO_CMD="${PGO_CMD-kubectl}" diff --git a/installers/kubectl/postgres-operator-ocp311.yml b/installers/kubectl/postgres-operator-ocp311.yml index 6d5af0b409..c028fe4cc4 100644 --- a/installers/kubectl/postgres-operator-ocp311.yml +++ b/installers/kubectl/postgres-operator-ocp311.yml @@ -50,7 +50,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "ubi8-13.7-4.6.7" + ccp_image_tag: "ubi8-13.8-4.6.8" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -83,14 +83,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.7" + pgo_client_version: "4.6.8" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "ubi8-4.6.7" + pgo_image_tag: "ubi8-4.6.8" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -171,7 +171,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.7 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.8 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/kubectl/postgres-operator.yml b/installers/kubectl/postgres-operator.yml index a93c9029c2..2de7452db3 100644 --- a/installers/kubectl/postgres-operator.yml +++ b/installers/kubectl/postgres-operator.yml @@ -145,7 +145,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "ubi8-13.7-4.6.7" + ccp_image_tag: "ubi8-13.8-4.6.8" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -178,14 +178,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.7" + pgo_client_version: "4.6.8" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "ubi8-4.6.7" + pgo_image_tag: "ubi8-4.6.8" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -281,7 +281,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.7 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.8 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/ansible/README.md b/installers/metrics/ansible/README.md index 68748233c8..701cde5137 100644 --- a/installers/metrics/ansible/README.md +++ b/installers/metrics/ansible/README.md @@ -4,7 +4,7 @@ Crunchy Data

-Latest Release: 4.6.7 +Latest Release: 4.6.8 ## General diff --git a/installers/metrics/helm/Chart.yaml b/installers/metrics/helm/Chart.yaml index bb85550a43..cef0137a2e 100644 --- a/installers/metrics/helm/Chart.yaml +++ b/installers/metrics/helm/Chart.yaml @@ -3,6 +3,6 @@ name: postgres-operator-monitoring description: Install for Crunchy PostgreSQL Operator Monitoring type: application version: 0.2.0 -appVersion: 4.6.7 +appVersion: 4.6.8 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/docs/static/logos/pgo.svg diff --git a/installers/metrics/helm/helm_template.yaml b/installers/metrics/helm/helm_template.yaml index a55e99389b..452c093cfd 100644 --- a/installers/metrics/helm/helm_template.yaml +++ b/installers/metrics/helm/helm_template.yaml @@ -20,5 +20,5 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "ubi8-4.6.7" +pgo_image_tag: "ubi8-4.6.8" diff --git a/installers/metrics/helm/values.yaml b/installers/metrics/helm/values.yaml index 7759d36ab9..c0e6af5105 100644 --- a/installers/metrics/helm/values.yaml +++ b/installers/metrics/helm/values.yaml @@ -20,7 +20,7 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "ubi8-4.6.7" +pgo_image_tag: "ubi8-4.6.8" # ===================== # Configuration Options diff --git a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml index 3a4fbd0e1f..603374855e 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml @@ -101,7 +101,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.7 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.8 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/kubectl/postgres-operator-metrics.yml b/installers/metrics/kubectl/postgres-operator-metrics.yml index c1f2af57a1..6e389ee59e 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics.yml @@ -171,7 +171,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.7 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.8 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/olm/Makefile b/installers/olm/Makefile index ca4dc1f166..f864bed69c 100644 --- a/installers/olm/Makefile +++ b/installers/olm/Makefile @@ -2,7 +2,7 @@ .SUFFIXES: CCP_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -CCP_PG_FULLVERSION ?= 13.7 +CCP_PG_FULLVERSION ?= 13.8 CCP_POSTGIS_VERSION ?= 3.0 CONTAINER ?= docker KUBECONFIG ?= $(HOME)/.kube/config @@ -11,7 +11,7 @@ OLM_TOOLS ?= registry.localhost:5000/postgres-operator-olm-tools:$(OLM_SDK_VERSI OLM_VERSION ?= 0.15.1 PGO_BASEOS ?= ubi8 PGO_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -PGO_VERSION ?= 4.6.7 +PGO_VERSION ?= 4.6.8 PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) CCP_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(PGO_VERSION) CCP_POSTGIS_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(CCP_POSTGIS_VERSION)-$(PGO_VERSION) diff --git a/installers/olm/description.openshift.md b/installers/olm/description.openshift.md index 5ceeac8b03..6a47cc45e8 100644 --- a/installers/olm/description.openshift.md +++ b/installers/olm/description.openshift.md @@ -188,7 +188,7 @@ spec: annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: ${cluster_image_prefix} - ccpimagetag: ubi8-13.7-${PGO_VERSION} + ccpimagetag: ubi8-13.8-${PGO_VERSION} clustername: ${pgo_cluster_name} database: ${pgo_cluster_name} exporterport: "9187" diff --git a/installers/olm/description.upstream.md b/installers/olm/description.upstream.md index c22eae4918..517f94e97f 100644 --- a/installers/olm/description.upstream.md +++ b/installers/olm/description.upstream.md @@ -168,7 +168,7 @@ spec: annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: ${cluster_image_prefix} - ccpimagetag: ubi8-13.7-${PGO_VERSION} + ccpimagetag: ubi8-13.8-${PGO_VERSION} clustername: ${pgo_cluster_name} database: ${pgo_cluster_name} exporterport: "9187" diff --git a/pkg/apis/crunchydata.com/v1/doc.go b/pkg/apis/crunchydata.com/v1/doc.go index a376ec5a77..fe5a96ee1f 100644 --- a/pkg/apis/crunchydata.com/v1/doc.go +++ b/pkg/apis/crunchydata.com/v1/doc.go @@ -53,7 +53,7 @@ cluster. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.7", + '{"ClientVersion":"4.6.8", "Namespace":"pgouser1", "Name":"mycluster", $PGO_APISERVER_URL/clusters @@ -72,7 +72,7 @@ show all of the clusters that are in the given namespace. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.7", + '{"ClientVersion":"4.6.8", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/showclusters @@ -82,7 +82,7 @@ $PGO_APISERVER_URL/showclusters curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ POST --data \ - '{"ClientVersion":"4.6.7", + '{"ClientVersion":"4.6.8", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/clustersdelete @@ -90,7 +90,7 @@ $PGO_APISERVER_URL/clustersdelete Schemes: http, https BasePath: / - Version: 4.6.7 + Version: 4.6.8 License: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0 Contact: Crunchy Data https://www.crunchydata.com/ diff --git a/pkg/apiservermsgs/common.go b/pkg/apiservermsgs/common.go index f9111ee8c3..8f223e5ac6 100644 --- a/pkg/apiservermsgs/common.go +++ b/pkg/apiservermsgs/common.go @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -const PGO_VERSION = "4.6.7" +const PGO_VERSION = "4.6.8" // Ok status const Ok = "ok" diff --git a/redhat/atomic/help.1 b/redhat/atomic/help.1 index 781a2502f4..1d1c2c7bd4 100644 --- a/redhat/atomic/help.1 +++ b/redhat/atomic/help.1 @@ -56,4 +56,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa \fB\fCRelease=\fR .PP -The specific release number of the container. For example, Release="4.6.7" +The specific release number of the container. For example, Release="4.6.8" diff --git a/redhat/atomic/help.md b/redhat/atomic/help.md index 39be30fb22..bcbf268c4a 100644 --- a/redhat/atomic/help.md +++ b/redhat/atomic/help.md @@ -45,4 +45,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa `Release=` -The specific release number of the container. For example, Release="4.6.7" +The specific release number of the container. For example, Release="4.6.8" From a324068ac8a20eea23589addcf07c87c22f13923 Mon Sep 17 00:00:00 2001 From: Andrew L'Ecuyer Date: Mon, 15 Aug 2022 20:15:18 +0000 Subject: [PATCH 261/276] Fix timescale & partman Versions in Release Notes Issue: [sc-15290] --- docs/content/releases/4.6.8.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/releases/4.6.8.md b/docs/content/releases/4.6.8.md index f4fa235b88..1892dfb501 100644 --- a/docs/content/releases/4.6.8.md +++ b/docs/content/releases/4.6.8.md @@ -13,5 +13,5 @@ PostgreSQL Operator 4.6.8 release includes the following software versions upgra - [PostgreSQL](https://www.postgresql.org) versions 13.8, 12.12, 11.17, and 10.22 are now available. - [PgBouncer](https://www.pgbouncer.org/) is now at version 1.17. -- The [pg_partman](https://github.com/pgpartman/pg_partman) extension is now at version 4.6.0. -- The [TimescaleDB](https://github.com/timescale/timescaledb) extension is now at version 2.7.1. +- The [pg_partman](https://github.com/pgpartman/pg_partman) extension is now at version 4.6.2. +- The [TimescaleDB](https://github.com/timescale/timescaledb) extension is now at version 2.7.2. From 7b75ec34944e8d5e06d18373a15763aad28ceaed Mon Sep 17 00:00:00 2001 From: Anthony Landreth Date: Wed, 12 Oct 2022 20:44:15 +0000 Subject: [PATCH 262/276] Adds Sleep to Cluster Annotation Test The requireClusterReady helper in suite_helpers_test.go does not always wait until the cluster is truly ready. Consequently, 'on_update' in cluster_annotation_test.go flakes, attempting to run update on a pod that isn't ready. [sc-15828] --- testing/pgo_cli/cluster_annotation_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/testing/pgo_cli/cluster_annotation_test.go b/testing/pgo_cli/cluster_annotation_test.go index 66c94898f1..d31159c5e8 100644 --- a/testing/pgo_cli/cluster_annotation_test.go +++ b/testing/pgo_cli/cluster_annotation_test.go @@ -216,6 +216,9 @@ func TestClusterAnnotation(t *testing.T) { requirePgBouncerReady(t, namespace(), test.testName, (2 * time.Minute)) } + // Allow pods time to reach ready status before running update. + time.Sleep(time.Minute + 30*time.Second) + updateCMD := []string{"update", "cluster", test.testName, "-n", namespace(), "--no-prompt"} updateCMD = append(updateCMD, test.addFlags...) output, err = pgo(updateCMD...).Exec(t) From fb5984947ebdfa0e8747aa8917477c0b9d8ec5d2 Mon Sep 17 00:00:00 2001 From: Anthony Landreth Date: Thu, 10 Nov 2022 14:35:23 -0500 Subject: [PATCH 263/276] Release Prep for v4.6.9 Issue: [sc-16495] --- Makefile | 4 ++-- README.md | 2 +- bin/push-ccp-to-gcr.sh | 2 +- conf/postgres-operator/pgo.yaml | 4 ++-- docs/config.toml | 12 ++++++------ docs/content/Configuration/compatibility.md | 5 +++++ docs/content/releases/4.6.9.md | 16 ++++++++++++++++ installers/ansible/README.md | 2 +- installers/ansible/values.yaml | 6 +++--- installers/gcp-marketplace/Makefile | 2 +- installers/gcp-marketplace/README.md | 2 +- installers/gcp-marketplace/values.yaml | 6 +++--- installers/helm/Chart.yaml | 2 +- installers/helm/values.yaml | 6 +++--- installers/kubectl/client-setup.sh | 2 +- .../kubectl/postgres-operator-ocp311.yml | 8 ++++---- installers/kubectl/postgres-operator.yml | 8 ++++---- installers/metrics/ansible/README.md | 2 +- installers/metrics/helm/Chart.yaml | 2 +- installers/metrics/helm/helm_template.yaml | 2 +- installers/metrics/helm/values.yaml | 2 +- .../postgres-operator-metrics-ocp311.yml | 2 +- .../kubectl/postgres-operator-metrics.yml | 2 +- installers/olm/Makefile | 4 ++-- installers/olm/description.openshift.md | 2 +- installers/olm/description.upstream.md | 2 +- pkg/apis/crunchydata.com/v1/doc.go | 19 +++++++++++-------- pkg/apiservermsgs/common.go | 2 +- redhat/atomic/help.1 | 2 +- redhat/atomic/help.md | 2 +- 30 files changed, 79 insertions(+), 55 deletions(-) create mode 100644 docs/content/releases/4.6.9.md diff --git a/Makefile b/Makefile index 9c88aec67e..49127a7172 100644 --- a/Makefile +++ b/Makefile @@ -4,9 +4,9 @@ PGOROOT ?= $(CURDIR) PGO_BASEOS ?= ubi8 PGO_IMAGE_PREFIX ?= crunchydata PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) -PGO_VERSION ?= 4.6.8 +PGO_VERSION ?= 4.6.9 PGO_PG_VERSION ?= 13 -PGO_PG_FULLVERSION ?= 13.8 +PGO_PG_FULLVERSION ?= 13.9 PGO_BACKREST_VERSION ?= 2.31 PACKAGER ?= yum diff --git a/README.md b/README.md index 7ee8f3148f..f114f444ed 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ to start as quickly as: ```shell kubectl create namespace pgo -kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.8/installers/kubectl/postgres-operator.yml +kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.9/installers/kubectl/postgres-operator.yml ``` Otherwise, we highly recommend following the instructions from our [Quickstart](https://access.crunchydata.com/documentation/postgres-operator/latest/quickstart/). diff --git a/bin/push-ccp-to-gcr.sh b/bin/push-ccp-to-gcr.sh index 3b6447e745..a92f74dad5 100755 --- a/bin/push-ccp-to-gcr.sh +++ b/bin/push-ccp-to-gcr.sh @@ -16,7 +16,7 @@ GCR_IMAGE_PREFIX=gcr.io/crunchy-dev-test CCP_IMAGE_PREFIX=crunchydata -CCP_IMAGE_TAG=ubi8-13.8-4.6.8 +CCP_IMAGE_TAG=ubi8-13.9-4.6.9 IMAGES=( crunchy-prometheus diff --git a/conf/postgres-operator/pgo.yaml b/conf/postgres-operator/pgo.yaml index bc44937057..69dce2c695 100644 --- a/conf/postgres-operator/pgo.yaml +++ b/conf/postgres-operator/pgo.yaml @@ -2,7 +2,7 @@ Cluster: CCPImagePrefix: registry.developers.crunchydata.com/crunchydata Metrics: false Badger: false - CCPImageTag: ubi8-13.8-4.6.8 + CCPImageTag: ubi8-13.9-4.6.9 Port: 5432 PGBadgerPort: 10000 ExporterPort: 9187 @@ -80,4 +80,4 @@ Storage: Pgo: Audit: false PGOImagePrefix: registry.developers.crunchydata.com/crunchydata - PGOImageTag: ubi8-4.6.8 + PGOImageTag: ubi8-4.6.9 diff --git a/docs/config.toml b/docs/config.toml index b98e64df3f..f4a4601417 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -25,12 +25,12 @@ disableNavChevron = false # set true to hide next/prev chevron, default is false highlightClientSide = false # set true to use highlight.pack.js instead of the default hugo chroma highlighter menushortcutsnewtab = true # set true to open shortcuts links to a new tab/window enableGitInfo = true -operatorVersion = "4.6.8" -postgresVersion = "13.8" -postgresVersion13 = "13.8" -postgresVersion12 = "12.12" -postgresVersion11 = "11.17" -postgresVersion10 = "10.22" +operatorVersion = "4.6.9" +postgresVersion = "13.9" +postgresVersion13 = "13.9" +postgresVersion12 = "12.13" +postgresVersion11 = "11.18" +postgresVersion10 = "10.23" postgisVersion = "3.0" ubiBase = "ubi8" diff --git a/docs/content/Configuration/compatibility.md b/docs/content/Configuration/compatibility.md index 0f87f58c9b..28576060cb 100644 --- a/docs/content/Configuration/compatibility.md +++ b/docs/content/Configuration/compatibility.md @@ -12,6 +12,11 @@ version dependencies between the two projects. Below are the operator releases a | Operator Release | Container Release | Postgres | PgBackrest Version |:----------|:-------------|:------------|:-------------- +| 4.6.9 | 4.6.9 | 13.9 | 2.31 | +|||12.13|2.31| +|||11.18|2.31| +|||10.23|2.31| +|||| | 4.6.8 | 4.6.8 | 13.8 | 2.31 | |||12.12|2.31| |||11.17|2.31| diff --git a/docs/content/releases/4.6.9.md b/docs/content/releases/4.6.9.md new file mode 100644 index 0000000000..0734b9eba7 --- /dev/null +++ b/docs/content/releases/4.6.9.md @@ -0,0 +1,16 @@ +--- +title: "4.6.9" +date: +draft: false +weight: 51 +--- + +Crunchy Data announces the release of PGO, the Postgres Operator 4.6.9. + +The PostgreSQL Operator is released in conjunction with the [Crunchy Container Suite](https://github.com/CrunchyData/crunchy-containers/). + +PostgreSQL Operator 4.6.9 release includes the following software versions upgrades: + +- [PostgreSQL](https://www.postgresql.org) versions 13.9, 12.13, 11.18, and 10.23 are now available. +- The [pg_partman](https://github.com/pgpartman/pg_partman) extension is now at version 4.7.1. +- The [TimescaleDB](https://github.com/timescale/timescaledb) extension is now at version 2.8.1. diff --git a/installers/ansible/README.md b/installers/ansible/README.md index 283de422e2..53e7de13d5 100644 --- a/installers/ansible/README.md +++ b/installers/ansible/README.md @@ -4,7 +4,7 @@ PGO: The Postgres Operator from Crunchy Data

-Latest Release: 4.6.8 +Latest Release: 4.6.9 ## General diff --git a/installers/ansible/values.yaml b/installers/ansible/values.yaml index 08d39410c4..062f93c08e 100644 --- a/installers/ansible/values.yaml +++ b/installers/ansible/values.yaml @@ -17,7 +17,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "ubi8-13.8-4.6.8" +ccp_image_tag: "ubi8-13.9-4.6.9" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -49,14 +49,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.8" +pgo_client_version: "4.6.9" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "ubi8-4.6.8" +pgo_image_tag: "ubi8-4.6.9" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/gcp-marketplace/Makefile b/installers/gcp-marketplace/Makefile index 0b8fef0a29..f51929da83 100644 --- a/installers/gcp-marketplace/Makefile +++ b/installers/gcp-marketplace/Makefile @@ -6,7 +6,7 @@ MARKETPLACE_TOOLS ?= gcr.io/cloud-marketplace-tools/k8s/dev:$(MARKETPLACE_VERSIO MARKETPLACE_VERSION ?= 0.9.4 KUBECONFIG ?= $(HOME)/.kube/config PARAMETERS ?= {} -PGO_VERSION ?= 4.6.8 +PGO_VERSION ?= 4.6.9 IMAGE_BUILD_ARGS = --build-arg MARKETPLACE_VERSION='$(MARKETPLACE_VERSION)' \ --build-arg PGO_VERSION='$(PGO_VERSION)' diff --git a/installers/gcp-marketplace/README.md b/installers/gcp-marketplace/README.md index c2c926ea0b..1307eb5345 100644 --- a/installers/gcp-marketplace/README.md +++ b/installers/gcp-marketplace/README.md @@ -59,7 +59,7 @@ Google Cloud Marketplace. ```shell IMAGE_REPOSITORY=gcr.io/crunchydata-public/postgres-operator - export PGO_VERSION=4.6.8 + export PGO_VERSION=4.6.9 export INSTALLER_IMAGE=${IMAGE_REPOSITORY}/deployer:${PGO_VERSION} export OPERATOR_IMAGE=${IMAGE_REPOSITORY}:${PGO_VERSION} export OPERATOR_IMAGE_API=${IMAGE_REPOSITORY}/pgo-apiserver:${PGO_VERSION} diff --git a/installers/gcp-marketplace/values.yaml b/installers/gcp-marketplace/values.yaml index e3a359919e..5ddfc26c50 100644 --- a/installers/gcp-marketplace/values.yaml +++ b/installers/gcp-marketplace/values.yaml @@ -10,7 +10,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "ubi8-13.8-4.6.8" +ccp_image_tag: "ubi8-13.9-4.6.9" create_rbac: "true" db_name: "" db_password_age_days: "0" @@ -32,9 +32,9 @@ pgo_admin_role_name: "pgoadmin" pgo_admin_username: "admin" pgo_client_container_install: "false" pgo_client_install: 'false' -pgo_client_version: "4.6.8" +pgo_client_version: "4.6.9" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "ubi8-4.6.8" +pgo_image_tag: "ubi8-4.6.9" pgo_installation_name: '${OPERATOR_NAME}' pgo_operator_namespace: '${OPERATOR_NAMESPACE}' scheduler_timeout: "3600" diff --git a/installers/helm/Chart.yaml b/installers/helm/Chart.yaml index c34e5cf578..2baf87773b 100644 --- a/installers/helm/Chart.yaml +++ b/installers/helm/Chart.yaml @@ -3,7 +3,7 @@ name: postgres-operator description: 'PGO: The Postgres Operator from Crunchy Data Helm Chart for Kubernetes' type: application version: 0.2.0 -appVersion: 4.6.8 +appVersion: 4.6.9 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/docs/static/logos/pgo.svg keywords: diff --git a/installers/helm/values.yaml b/installers/helm/values.yaml index 4be0d09151..cc0c4e5753 100644 --- a/installers/helm/values.yaml +++ b/installers/helm/values.yaml @@ -37,7 +37,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "ubi8-13.8-4.6.8" +ccp_image_tag: "ubi8-13.9-4.6.9" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -69,14 +69,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.8" +pgo_client_version: "4.6.9" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "ubi8-4.6.8" +pgo_image_tag: "ubi8-4.6.9" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/kubectl/client-setup.sh b/installers/kubectl/client-setup.sh index 8140ec366d..dc60c69b95 100755 --- a/installers/kubectl/client-setup.sh +++ b/installers/kubectl/client-setup.sh @@ -14,7 +14,7 @@ # This script should be run after the operator has been deployed PGO_OPERATOR_NAMESPACE="${PGO_OPERATOR_NAMESPACE:-pgo}" PGO_USER_ADMIN="${PGO_USER_ADMIN:-pgouser-admin}" -PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.8}" +PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.9}" PGO_CLIENT_URL="https://github.com/CrunchyData/postgres-operator/releases/download/${PGO_CLIENT_VERSION}" PGO_CMD="${PGO_CMD-kubectl}" diff --git a/installers/kubectl/postgres-operator-ocp311.yml b/installers/kubectl/postgres-operator-ocp311.yml index c028fe4cc4..56d86b49cf 100644 --- a/installers/kubectl/postgres-operator-ocp311.yml +++ b/installers/kubectl/postgres-operator-ocp311.yml @@ -50,7 +50,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "ubi8-13.8-4.6.8" + ccp_image_tag: "ubi8-13.9-4.6.9" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -83,14 +83,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.8" + pgo_client_version: "4.6.9" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "ubi8-4.6.8" + pgo_image_tag: "ubi8-4.6.9" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -171,7 +171,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.8 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.9 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/kubectl/postgres-operator.yml b/installers/kubectl/postgres-operator.yml index 2de7452db3..91600bd766 100644 --- a/installers/kubectl/postgres-operator.yml +++ b/installers/kubectl/postgres-operator.yml @@ -145,7 +145,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "ubi8-13.8-4.6.8" + ccp_image_tag: "ubi8-13.9-4.6.9" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -178,14 +178,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.8" + pgo_client_version: "4.6.9" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "ubi8-4.6.8" + pgo_image_tag: "ubi8-4.6.9" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -281,7 +281,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.8 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.9 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/ansible/README.md b/installers/metrics/ansible/README.md index 701cde5137..a89f03e1b6 100644 --- a/installers/metrics/ansible/README.md +++ b/installers/metrics/ansible/README.md @@ -4,7 +4,7 @@ Crunchy Data

-Latest Release: 4.6.8 +Latest Release: 4.6.9 ## General diff --git a/installers/metrics/helm/Chart.yaml b/installers/metrics/helm/Chart.yaml index cef0137a2e..9adcf6a15e 100644 --- a/installers/metrics/helm/Chart.yaml +++ b/installers/metrics/helm/Chart.yaml @@ -3,6 +3,6 @@ name: postgres-operator-monitoring description: Install for Crunchy PostgreSQL Operator Monitoring type: application version: 0.2.0 -appVersion: 4.6.8 +appVersion: 4.6.9 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/docs/static/logos/pgo.svg diff --git a/installers/metrics/helm/helm_template.yaml b/installers/metrics/helm/helm_template.yaml index 452c093cfd..663e9214eb 100644 --- a/installers/metrics/helm/helm_template.yaml +++ b/installers/metrics/helm/helm_template.yaml @@ -20,5 +20,5 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "ubi8-4.6.8" +pgo_image_tag: "ubi8-4.6.9" diff --git a/installers/metrics/helm/values.yaml b/installers/metrics/helm/values.yaml index c0e6af5105..4667d53c45 100644 --- a/installers/metrics/helm/values.yaml +++ b/installers/metrics/helm/values.yaml @@ -20,7 +20,7 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "ubi8-4.6.8" +pgo_image_tag: "ubi8-4.6.9" # ===================== # Configuration Options diff --git a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml index 603374855e..a95f6fe403 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml @@ -101,7 +101,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.8 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.9 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/kubectl/postgres-operator-metrics.yml b/installers/metrics/kubectl/postgres-operator-metrics.yml index 6e389ee59e..115362dcc6 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics.yml @@ -171,7 +171,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.8 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.9 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/olm/Makefile b/installers/olm/Makefile index f864bed69c..63e94b47ee 100644 --- a/installers/olm/Makefile +++ b/installers/olm/Makefile @@ -2,7 +2,7 @@ .SUFFIXES: CCP_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -CCP_PG_FULLVERSION ?= 13.8 +CCP_PG_FULLVERSION ?= 13.9 CCP_POSTGIS_VERSION ?= 3.0 CONTAINER ?= docker KUBECONFIG ?= $(HOME)/.kube/config @@ -11,7 +11,7 @@ OLM_TOOLS ?= registry.localhost:5000/postgres-operator-olm-tools:$(OLM_SDK_VERSI OLM_VERSION ?= 0.15.1 PGO_BASEOS ?= ubi8 PGO_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -PGO_VERSION ?= 4.6.8 +PGO_VERSION ?= 4.6.9 PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) CCP_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(PGO_VERSION) CCP_POSTGIS_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(CCP_POSTGIS_VERSION)-$(PGO_VERSION) diff --git a/installers/olm/description.openshift.md b/installers/olm/description.openshift.md index 6a47cc45e8..6b54bbbacf 100644 --- a/installers/olm/description.openshift.md +++ b/installers/olm/description.openshift.md @@ -188,7 +188,7 @@ spec: annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: ${cluster_image_prefix} - ccpimagetag: ubi8-13.8-${PGO_VERSION} + ccpimagetag: ubi8-13.9-${PGO_VERSION} clustername: ${pgo_cluster_name} database: ${pgo_cluster_name} exporterport: "9187" diff --git a/installers/olm/description.upstream.md b/installers/olm/description.upstream.md index 517f94e97f..cb7a2c9771 100644 --- a/installers/olm/description.upstream.md +++ b/installers/olm/description.upstream.md @@ -168,7 +168,7 @@ spec: annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: ${cluster_image_prefix} - ccpimagetag: ubi8-13.8-${PGO_VERSION} + ccpimagetag: ubi8-13.9-${PGO_VERSION} clustername: ${pgo_cluster_name} database: ${pgo_cluster_name} exporterport: "9187" diff --git a/pkg/apis/crunchydata.com/v1/doc.go b/pkg/apis/crunchydata.com/v1/doc.go index fe5a96ee1f..e20a4a55f5 100644 --- a/pkg/apis/crunchydata.com/v1/doc.go +++ b/pkg/apis/crunchydata.com/v1/doc.go @@ -52,8 +52,9 @@ cluster. ``` curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ + POST --data \ - '{"ClientVersion":"4.6.8", + '{"ClientVersion":"4.6.9", "Namespace":"pgouser1", "Name":"mycluster", $PGO_APISERVER_URL/clusters @@ -71,8 +72,9 @@ show all of the clusters that are in the given namespace. ``` curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ + POST --data \ - '{"ClientVersion":"4.6.8", + '{"ClientVersion":"4.6.9", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/showclusters @@ -81,8 +83,9 @@ $PGO_APISERVER_URL/showclusters ``` curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ + POST --data \ - '{"ClientVersion":"4.6.8", + '{"ClientVersion":"4.6.9", "Namespace":"pgouser1", "Clustername":"mycluster"}' \ $PGO_APISERVER_URL/clustersdelete @@ -90,16 +93,16 @@ $PGO_APISERVER_URL/clustersdelete Schemes: http, https BasePath: / - Version: 4.6.8 + Version: 4.6.9 License: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0 Contact: Crunchy Data https://www.crunchydata.com/ - Consumes: - - application/json + Consumes: + - application/json - Produces: - - application/json + Produces: + - application/json swagger:meta */ diff --git a/pkg/apiservermsgs/common.go b/pkg/apiservermsgs/common.go index 8f223e5ac6..ac3b79aed1 100644 --- a/pkg/apiservermsgs/common.go +++ b/pkg/apiservermsgs/common.go @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -const PGO_VERSION = "4.6.8" +const PGO_VERSION = "4.6.9" // Ok status const Ok = "ok" diff --git a/redhat/atomic/help.1 b/redhat/atomic/help.1 index 1d1c2c7bd4..edb99b1d09 100644 --- a/redhat/atomic/help.1 +++ b/redhat/atomic/help.1 @@ -56,4 +56,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa \fB\fCRelease=\fR .PP -The specific release number of the container. For example, Release="4.6.8" +The specific release number of the container. For example, Release="4.6.9" diff --git a/redhat/atomic/help.md b/redhat/atomic/help.md index bcbf268c4a..5b1a898e4b 100644 --- a/redhat/atomic/help.md +++ b/redhat/atomic/help.md @@ -45,4 +45,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa `Release=` -The specific release number of the container. For example, Release="4.6.8" +The specific release number of the container. For example, Release="4.6.9" From 593051992980c2f5fab9a1e7ffd6e81222707227 Mon Sep 17 00:00:00 2001 From: Andrew L'Ecuyer Date: Thu, 17 Nov 2022 19:04:47 +0000 Subject: [PATCH 264/276] Pin 'kubectl' to v1.24 in the Deployer Image Issue: [sc-16731] --- build/pgo-deployer/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pgo-deployer/Dockerfile b/build/pgo-deployer/Dockerfile index 711ef41fb4..3ebc8beb1e 100644 --- a/build/pgo-deployer/Dockerfile +++ b/build/pgo-deployer/Dockerfile @@ -17,7 +17,7 @@ RUN if [ "$DFSET" = "centos" ] ; then \ ${PACKAGER} install -y epel-release \ && ${PACKAGER} -y install \ --setopt=skip_missing_names_on_install=False \ - kubectl \ + kubectl-1.24* \ ansible \ which \ gettext \ From d6fe5fceda6b8a21d8de1da0b1a67511c1a6fd71 Mon Sep 17 00:00:00 2001 From: Anthony Landreth Date: Thu, 1 Dec 2022 20:54:23 +0000 Subject: [PATCH 265/276] Bumps jmespath Upgrades from python38-jmespath to python39-jmespath. --- build/pgo-deployer/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pgo-deployer/Dockerfile b/build/pgo-deployer/Dockerfile index 3ebc8beb1e..83ae6930eb 100644 --- a/build/pgo-deployer/Dockerfile +++ b/build/pgo-deployer/Dockerfile @@ -60,7 +60,7 @@ RUN if [ "$BASEOS" = "ubi8" ] ; then \ ansible \ which \ gettext \ - python38-jmespath \ + python39-jmespath \ && ${PACKAGER} -y clean all --enablerepo='rhocp-4.5-for-rhel-8-x86_64-rpms' ; \ fi From 97ef324c3922b09ea9f1bc31179be6fe5765eb76 Mon Sep 17 00:00:00 2001 From: ValClarkson Date: Fri, 20 Jan 2023 12:23:38 -0500 Subject: [PATCH 266/276] remove ref of pg10 from documentation Issue [sc-17463] --- docs/config.toml | 1 - docs/content/advanced/crunchy-postgres-exporter.md | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/config.toml b/docs/config.toml index f4a4601417..048660a1b3 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -30,7 +30,6 @@ postgresVersion = "13.9" postgresVersion13 = "13.9" postgresVersion12 = "12.13" postgresVersion11 = "11.18" -postgresVersion10 = "10.23" postgisVersion = "3.0" ubiBase = "ubi8" diff --git a/docs/content/advanced/crunchy-postgres-exporter.md b/docs/content/advanced/crunchy-postgres-exporter.md index bc31829bc5..abfd1f63c4 100644 --- a/docs/content/advanced/crunchy-postgres-exporter.md +++ b/docs/content/advanced/crunchy-postgres-exporter.md @@ -23,7 +23,7 @@ can be specified for the API to collect. For an example of a queries.yml file, s The crunchy-postgres-exporter Docker image contains the following packages (versions vary depending on PostgreSQL version): -* PostgreSQL ({{< param postgresVersion13 >}}, {{< param postgresVersion12 >}}, {{< param postgresVersion11 >}}, and {{< param postgresVersion10 >}}. +* PostgreSQL ({{< param postgresVersion13 >}}, {{< param postgresVersion12 >}}, and {{< param postgresVersion11 >}}). * UBI 8 - publicly available * UBI 7, UBI 8 - customers only * [PostgreSQL Exporter](https://github.com/wrouesnel/postgres_exporter) From 4436028c04f654ffe41a3c5f76962c98b810b718 Mon Sep 17 00:00:00 2001 From: TJ Moore Date: Tue, 31 Jan 2023 17:51:42 -0500 Subject: [PATCH 267/276] Add a PGO CLI test for pgAdmin for V4 Issue: [sc-17594] --- testing/pgo_cli/cluster_pgadmin_test.go | 106 ++++++++++++++++++++++++ testing/pgo_cli/suite_helpers_test.go | 31 +++++++ 2 files changed, 137 insertions(+) create mode 100644 testing/pgo_cli/cluster_pgadmin_test.go diff --git a/testing/pgo_cli/cluster_pgadmin_test.go b/testing/pgo_cli/cluster_pgadmin_test.go new file mode 100644 index 0000000000..627beeeace --- /dev/null +++ b/testing/pgo_cli/cluster_pgadmin_test.go @@ -0,0 +1,106 @@ +package pgo_cli_test + +/* + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. + 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. +*/ + +import ( + "strings" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestClusterPgAdmin(t *testing.T) { + t.Parallel() + + var pgadminOnce sync.Once + requirePgAdmin := func(t *testing.T, namespace, cluster string) { + pgadminOnce.Do(func() { + output, err := pgo("create", "pgadmin", cluster, "-n", namespace).Exec(t) + require.NoError(t, err) + require.Contains(t, output, "addition scheduled") + }) + } + + withNamespace(t, func(namespace func() string) { + withCluster(t, namespace, func(cluster func() string) { + t.Run("create pgadmin", func(t *testing.T) { + t.Run("starts PgAdmin", func(t *testing.T) { + requireClusterReady(t, namespace(), cluster(), time.Minute) + requirePgAdmin(t, namespace(), cluster()) + + // PgAdmin does not appear immediately. + requirePgAdminReady(t, namespace(), cluster(), time.Minute) + + // Here we wait 5 seconds to ensure pgAdmin has deployed in a stable way. + // Without this sleep, the test can pass because the pgAdmin container is + // (briefly) stable. + time.Sleep(time.Duration(5) * time.Second) + + output, err := pgo("show", "cluster", cluster(), "-n", namespace()).Exec(t) + require.NoError(t, err) + require.Contains(t, output, "pgadmin") + + output, err = pgo("test", cluster(), "-n", namespace()).Exec(t) + require.NoError(t, err) + require.Contains(t, output, "pgadmin", "expected PgAdmin to be discoverable") + + for _, line := range strings.Split(output, "\n") { + if strings.Contains(line, "pgadmin") { + require.Contains(t, line, "UP", "expected PgAdmin to be accessible") + } + } + }) + }) + + t.Run("delete pgadmin", func(t *testing.T) { + t.Run("stops PgAdmin", func(t *testing.T) { + requireClusterReady(t, namespace(), cluster(), time.Minute) + requirePgAdmin(t, namespace(), cluster()) + requirePgAdminReady(t, namespace(), cluster(), time.Minute) + + output, err := pgo("delete", "pgadmin", cluster(), "--no-prompt", "-n", namespace()).Exec(t) + require.NoError(t, err) + require.Contains(t, output, "delete scheduled") + + gone := func() bool { + deployments, err := TestContext.Kubernetes.ListDeployments(namespace(), map[string]string{ + "pg-cluster": cluster(), + "crunchy-pgadmin": "true", + }) + require.NoError(t, err) + return len(deployments) == 0 + } + requireWaitFor(t, gone, time.Minute, time.Second, + "timeout waiting for PgAdmin of %q in %q", cluster(), namespace()) + + output, err = pgo("show", "cluster", cluster(), "-n", namespace()).Exec(t) + require.NoError(t, err) + + //require.NotContains(t, output, "pgadmin") + for _, line := range strings.Split(output, "\n") { + // The service and deployment should be gone. The only remaining + // reference could be in the labels. + if strings.Contains(line, "pgadmin") { + require.Contains(t, line, "pgadmin=false") + } + } + }) + }) + }) + }) +} diff --git a/testing/pgo_cli/suite_helpers_test.go b/testing/pgo_cli/suite_helpers_test.go index 8ee0cce55c..f0d8a68448 100644 --- a/testing/pgo_cli/suite_helpers_test.go +++ b/testing/pgo_cli/suite_helpers_test.go @@ -198,6 +198,37 @@ func requireClusterReady(t testing.TB, namespace, cluster string, timeout time.D } } +// requirePgAdminReady waits until all PgAdmin deployments for cluster are +// ready. If timeout elapses or any error occurs, t will FailNow. +func requirePgAdminReady(t testing.TB, namespace, cluster string, timeout time.Duration) { + t.Helper() + + ready := func() bool { + deployments, err := TestContext.Kubernetes.ListDeployments(namespace, map[string]string{ + "pg-cluster": cluster, + "crunchy-pgadmin": "true", + }) + require.NoError(t, err) + + if len(deployments) == 0 { + return false + } + for _, deployment := range deployments { + if *deployment.Spec.Replicas < 1 || + deployment.Status.ReadyReplicas != *deployment.Spec.Replicas || + deployment.Status.UpdatedReplicas != *deployment.Spec.Replicas { + return false + } + } + return true + } + + if !ready() { + requireWaitFor(t, ready, timeout, time.Second, + "timeout waiting for PgAdmin of %q in %q", cluster, namespace) + } +} + // requirePgBouncerReady waits until all PgBouncer deployments for cluster are // ready. If timeout elapses or any error occurs, t will FailNow. func requirePgBouncerReady(t testing.TB, namespace, cluster string, timeout time.Duration) { From 2943aababb8fc20311f66e7cbdc8e8a192c4d9b2 Mon Sep 17 00:00:00 2001 From: Benjamin Blattberg Date: Tue, 14 Feb 2023 14:32:55 -0600 Subject: [PATCH 268/276] Update Copyright notices for 2023 (#3565) --- LICENSE.md | 2 +- bin/check-deps.sh | 2 +- bin/crunchy-postgres-exporter/common_lib.sh | 2 +- bin/crunchy-postgres-exporter/start.sh | 2 +- bin/get-deps.sh | 2 +- bin/get-pgmonitor.sh | 2 +- bin/license_aggregator.sh | 2 +- bin/pgo-event/pgo-event.sh | 2 +- bin/pgo-rmdata/start.sh | 2 +- bin/pgo-scheduler/start.sh | 2 +- bin/pgo-sqlrunner/start.sh | 2 +- bin/pre-pull-crunchy-containers.sh | 2 +- bin/pull-from-gcr.sh | 2 +- bin/push-ccp-to-gcr.sh | 2 +- bin/push-to-gcr.sh | 2 +- bin/uid_daemon.sh | 2 +- bin/upgrade-secret.sh | 2 +- cmd/apiserver/main.go | 2 +- cmd/pgo-rmdata/main.go | 2 +- cmd/pgo-rmdata/process.go | 2 +- cmd/pgo-rmdata/types.go | 2 +- cmd/pgo-scheduler/main.go | 2 +- cmd/pgo-scheduler/scheduler/configmapcontroller.go | 2 +- cmd/pgo-scheduler/scheduler/controllermanager.go | 2 +- cmd/pgo-scheduler/scheduler/pgbackrest.go | 2 +- cmd/pgo-scheduler/scheduler/policy.go | 2 +- cmd/pgo-scheduler/scheduler/scheduler.go | 2 +- cmd/pgo-scheduler/scheduler/tasks.go | 2 +- cmd/pgo-scheduler/scheduler/types.go | 2 +- cmd/pgo-scheduler/scheduler/validate.go | 2 +- cmd/pgo-scheduler/scheduler/validate_test.go | 2 +- cmd/pgo/api/backrest.go | 2 +- cmd/pgo/api/cat.go | 2 +- cmd/pgo/api/cluster.go | 2 +- cmd/pgo/api/common.go | 2 +- cmd/pgo/api/config.go | 2 +- cmd/pgo/api/df.go | 2 +- cmd/pgo/api/failover.go | 2 +- cmd/pgo/api/label.go | 2 +- cmd/pgo/api/namespace.go | 2 +- cmd/pgo/api/pgadmin.go | 2 +- cmd/pgo/api/pgbouncer.go | 2 +- cmd/pgo/api/pgdump.go | 2 +- cmd/pgo/api/pgorole.go | 2 +- cmd/pgo/api/pgouser.go | 2 +- cmd/pgo/api/policy.go | 2 +- cmd/pgo/api/pvc.go | 2 +- cmd/pgo/api/reload.go | 2 +- cmd/pgo/api/restart.go | 2 +- cmd/pgo/api/restore.go | 2 +- cmd/pgo/api/restoreDump.go | 2 +- cmd/pgo/api/scale.go | 2 +- cmd/pgo/api/scaledown.go | 2 +- cmd/pgo/api/schedule.go | 2 +- cmd/pgo/api/status.go | 2 +- cmd/pgo/api/test.go | 2 +- cmd/pgo/api/upgrade.go | 2 +- cmd/pgo/api/user.go | 2 +- cmd/pgo/api/version.go | 2 +- cmd/pgo/api/workflow.go | 2 +- cmd/pgo/cmd/auth.go | 2 +- cmd/pgo/cmd/backrest.go | 2 +- cmd/pgo/cmd/backup.go | 2 +- cmd/pgo/cmd/cat.go | 2 +- cmd/pgo/cmd/cluster.go | 2 +- cmd/pgo/cmd/common.go | 2 +- cmd/pgo/cmd/config.go | 2 +- cmd/pgo/cmd/create.go | 2 +- cmd/pgo/cmd/delete.go | 2 +- cmd/pgo/cmd/df.go | 2 +- cmd/pgo/cmd/failover.go | 2 +- cmd/pgo/cmd/flags.go | 2 +- cmd/pgo/cmd/label.go | 2 +- cmd/pgo/cmd/namespace.go | 2 +- cmd/pgo/cmd/pgadmin.go | 2 +- cmd/pgo/cmd/pgbouncer.go | 2 +- cmd/pgo/cmd/pgdump.go | 2 +- cmd/pgo/cmd/pgorole.go | 2 +- cmd/pgo/cmd/pgouser.go | 2 +- cmd/pgo/cmd/policy.go | 2 +- cmd/pgo/cmd/pvc.go | 2 +- cmd/pgo/cmd/reload.go | 2 +- cmd/pgo/cmd/restart.go | 2 +- cmd/pgo/cmd/restore.go | 2 +- cmd/pgo/cmd/root.go | 2 +- cmd/pgo/cmd/scale.go | 2 +- cmd/pgo/cmd/scaledown.go | 2 +- cmd/pgo/cmd/schedule.go | 2 +- cmd/pgo/cmd/show.go | 2 +- cmd/pgo/cmd/status.go | 2 +- cmd/pgo/cmd/test.go | 2 +- cmd/pgo/cmd/update.go | 2 +- cmd/pgo/cmd/upgrade.go | 2 +- cmd/pgo/cmd/user.go | 2 +- cmd/pgo/cmd/version.go | 2 +- cmd/pgo/cmd/watch.go | 2 +- cmd/pgo/cmd/workflow.go | 2 +- cmd/pgo/generatedocs.go | 2 +- cmd/pgo/main.go | 2 +- cmd/pgo/util/confirmation.go | 2 +- cmd/pgo/util/pad.go | 2 +- cmd/pgo/util/validation.go | 2 +- cmd/postgres-operator/main.go | 2 +- cmd/postgres-operator/open_telemetry.go | 2 +- deploy/add-targeted-namespace-reconcile-rbac.sh | 2 +- deploy/add-targeted-namespace.sh | 2 +- deploy/cleannamespaces.sh | 2 +- deploy/cleanup-rbac.sh | 2 +- deploy/cleanup.sh | 2 +- deploy/deploy.sh | 2 +- deploy/gen-api-keys.sh | 2 +- deploy/install-bootstrap-creds.sh | 2 +- deploy/install-rbac.sh | 2 +- deploy/remove-crd.sh | 2 +- deploy/setupnamespaces.sh | 2 +- deploy/show-crd.sh | 2 +- deploy/upgrade-creds.sh | 2 +- deploy/upgrade-pgo.sh | 2 +- docs/layouts/partials/flex/body-aftercontent.html | 2 +- examples/create-by-resource/run.sh | 2 +- examples/custom-config/create.sh | 2 +- examples/custom-config/setup.sql | 2 +- hack/boilerplate.go.txt | 2 +- hack/config_sync.sh | 2 +- hack/update-codegen.sh | 2 +- .../roles/pgo-operator/templates/add-targeted-namespace.sh.j2 | 2 +- installers/image/bin/pgo-deploy.sh | 2 +- installers/kubectl/client-setup.sh | 2 +- internal/apiserver/backrestservice/backrestimpl.go | 2 +- internal/apiserver/backrestservice/backrestservice.go | 2 +- internal/apiserver/backupoptions/backupoptionsutil.go | 2 +- internal/apiserver/backupoptions/backupoptionsutil_test.go | 2 +- internal/apiserver/backupoptions/pgbackrestoptions.go | 2 +- internal/apiserver/backupoptions/pgdumpoptions.go | 2 +- internal/apiserver/catservice/catimpl.go | 2 +- internal/apiserver/catservice/catservice.go | 2 +- internal/apiserver/clusterservice/clusterimpl.go | 2 +- internal/apiserver/clusterservice/clusterservice.go | 2 +- internal/apiserver/clusterservice/scaleimpl.go | 2 +- internal/apiserver/clusterservice/scaleservice.go | 2 +- internal/apiserver/common.go | 2 +- internal/apiserver/common_test.go | 2 +- internal/apiserver/configservice/configimpl.go | 2 +- internal/apiserver/configservice/configservice.go | 2 +- internal/apiserver/dfservice/dfimpl.go | 2 +- internal/apiserver/dfservice/dfservice.go | 2 +- internal/apiserver/failoverservice/failoverimpl.go | 2 +- internal/apiserver/failoverservice/failoverservice.go | 2 +- internal/apiserver/labelservice/labelimpl.go | 2 +- internal/apiserver/labelservice/labelservice.go | 2 +- internal/apiserver/middleware.go | 2 +- internal/apiserver/namespaceservice/namespaceimpl.go | 2 +- internal/apiserver/namespaceservice/namespaceservice.go | 2 +- internal/apiserver/perms.go | 2 +- internal/apiserver/pgadminservice/pgadminimpl.go | 2 +- internal/apiserver/pgadminservice/pgadminservice.go | 2 +- internal/apiserver/pgbouncerservice/pgbouncerimpl.go | 2 +- internal/apiserver/pgbouncerservice/pgbouncerservice.go | 2 +- internal/apiserver/pgdumpservice/pgdumpimpl.go | 2 +- internal/apiserver/pgdumpservice/pgdumpservice.go | 2 +- internal/apiserver/pgoroleservice/pgoroleimpl.go | 2 +- internal/apiserver/pgoroleservice/pgoroleservice.go | 2 +- internal/apiserver/pgouserservice/pgouserimpl.go | 2 +- internal/apiserver/pgouserservice/pgouserservice.go | 2 +- internal/apiserver/policyservice/policyimpl.go | 2 +- internal/apiserver/policyservice/policyservice.go | 2 +- internal/apiserver/pvcservice/pvcimpl.go | 2 +- internal/apiserver/pvcservice/pvcservice.go | 2 +- internal/apiserver/reloadservice/reloadimpl.go | 2 +- internal/apiserver/reloadservice/reloadservice.go | 2 +- internal/apiserver/restartservice/restartimpl.go | 2 +- internal/apiserver/restartservice/restartservice.go | 2 +- internal/apiserver/root.go | 2 +- internal/apiserver/routing/doc.go | 2 +- internal/apiserver/routing/routes.go | 2 +- internal/apiserver/scheduleservice/scheduleimpl.go | 2 +- internal/apiserver/scheduleservice/scheduleservice.go | 2 +- internal/apiserver/statusservice/statusimpl.go | 2 +- internal/apiserver/statusservice/statusservice.go | 2 +- internal/apiserver/upgradeservice/upgradeimpl.go | 2 +- internal/apiserver/upgradeservice/upgradeservice.go | 2 +- internal/apiserver/userservice/userimpl.go | 2 +- internal/apiserver/userservice/userimpl_test.go | 2 +- internal/apiserver/userservice/userservice.go | 2 +- internal/apiserver/versionservice/versionimpl.go | 2 +- internal/apiserver/versionservice/versionservice.go | 2 +- internal/apiserver/workflowservice/workflowimpl.go | 2 +- internal/apiserver/workflowservice/workflowservice.go | 2 +- internal/config/annotations.go | 2 +- internal/config/defaults.go | 2 +- internal/config/images.go | 2 +- internal/config/labels.go | 2 +- internal/config/pgoconfig.go | 2 +- internal/config/secrets.go | 2 +- internal/config/volumes.go | 2 +- internal/controller/configmap/configmapcontroller.go | 2 +- internal/controller/configmap/synchandler.go | 2 +- internal/controller/controllerutil.go | 2 +- internal/controller/job/backresthandler.go | 2 +- internal/controller/job/bootstraphandler.go | 2 +- internal/controller/job/jobcontroller.go | 2 +- internal/controller/job/jobevents.go | 2 +- internal/controller/job/jobutil.go | 2 +- internal/controller/job/pgdumphandler.go | 2 +- internal/controller/job/rmdatahandler.go | 2 +- internal/controller/manager/controllermanager.go | 2 +- internal/controller/manager/rbac.go | 2 +- internal/controller/namespace/namespacecontroller.go | 2 +- internal/controller/pgcluster/pgclustercontroller.go | 2 +- internal/controller/pgpolicy/pgpolicycontroller.go | 2 +- internal/controller/pgreplica/pgreplicacontroller.go | 2 +- internal/controller/pgtask/backresthandler.go | 2 +- internal/controller/pgtask/pgtaskcontroller.go | 2 +- internal/controller/pod/inithandler.go | 2 +- internal/controller/pod/podcontroller.go | 2 +- internal/controller/pod/podevents.go | 2 +- internal/controller/pod/promotionhandler.go | 2 +- internal/kubeapi/client_config.go | 2 +- internal/kubeapi/endpoints.go | 2 +- internal/kubeapi/errors.go | 2 +- internal/kubeapi/exec.go | 2 +- internal/kubeapi/fake/clientset.go | 2 +- internal/kubeapi/fake/fakeclients.go | 2 +- internal/kubeapi/patch.go | 2 +- internal/kubeapi/patch_test.go | 2 +- internal/kubeapi/volumes.go | 2 +- internal/kubeapi/volumes_test.go | 2 +- internal/logging/loglib.go | 2 +- internal/ns/nslogic.go | 2 +- internal/operator/backrest/backup.go | 2 +- internal/operator/backrest/repo.go | 2 +- internal/operator/backrest/restore.go | 2 +- internal/operator/backrest/stanza.go | 2 +- internal/operator/cluster/cluster.go | 2 +- internal/operator/cluster/clusterlogic.go | 2 +- internal/operator/cluster/common.go | 2 +- internal/operator/cluster/common_test.go | 2 +- internal/operator/cluster/exporter.go | 2 +- internal/operator/cluster/pgadmin.go | 2 +- internal/operator/cluster/pgbadger.go | 2 +- internal/operator/cluster/pgbouncer.go | 2 +- internal/operator/cluster/pgbouncer_test.go | 2 +- internal/operator/cluster/rolling.go | 2 +- internal/operator/cluster/service.go | 2 +- internal/operator/cluster/standby.go | 2 +- internal/operator/cluster/upgrade.go | 2 +- internal/operator/clusterutilities.go | 2 +- internal/operator/clusterutilities_test.go | 2 +- internal/operator/common.go | 2 +- internal/operator/common_test.go | 2 +- internal/operator/config/configutil.go | 2 +- internal/operator/config/dcs.go | 2 +- internal/operator/config/localdb.go | 2 +- internal/operator/failover.go | 2 +- internal/operator/failover_test.go | 2 +- internal/operator/operatorupgrade/version-check.go | 2 +- internal/operator/pgbackrest.go | 2 +- internal/operator/pgbackrest_test.go | 2 +- internal/operator/pgdump/dump.go | 2 +- internal/operator/pgdump/restore.go | 2 +- internal/operator/pvc/pvc.go | 2 +- internal/operator/storage.go | 2 +- internal/operator/storage_test.go | 2 +- internal/operator/switchover.go | 2 +- internal/operator/switchover_test.go | 2 +- internal/operator/task/applypolicies.go | 2 +- internal/operator/task/rmdata.go | 2 +- internal/operator/task/workflow.go | 2 +- internal/operator/wal.go | 2 +- internal/patroni/doc.go | 2 +- internal/patroni/patroni.go | 2 +- internal/pgadmin/backoff.go | 2 +- internal/pgadmin/backoff_test.go | 2 +- internal/pgadmin/crypto.go | 2 +- internal/pgadmin/crypto_test.go | 2 +- internal/pgadmin/doc.go | 2 +- internal/pgadmin/hash.go | 2 +- internal/pgadmin/logic.go | 2 +- internal/pgadmin/runner.go | 2 +- internal/pgadmin/server.go | 2 +- internal/postgres/doc.go | 2 +- internal/postgres/password/doc.go | 2 +- internal/postgres/password/md5.go | 2 +- internal/postgres/password/md5_test.go | 2 +- internal/postgres/password/password.go | 2 +- internal/postgres/password/password_test.go | 2 +- internal/postgres/password/scram.go | 2 +- internal/postgres/password/scram_test.go | 2 +- internal/tlsutil/primitives.go | 2 +- internal/tlsutil/primitives_test.go | 2 +- internal/util/backrest.go | 2 +- internal/util/cluster.go | 2 +- internal/util/cluster_test.go | 2 +- internal/util/exporter.go | 2 +- internal/util/exporter_test.go | 2 +- internal/util/failover.go | 2 +- internal/util/pgbouncer.go | 2 +- internal/util/policy.go | 2 +- internal/util/secrets.go | 2 +- internal/util/secrets_test.go | 2 +- internal/util/ssh.go | 2 +- internal/util/util.go | 2 +- pkg/apis/crunchydata.com/v1/cluster.go | 2 +- pkg/apis/crunchydata.com/v1/cluster_test.go | 2 +- pkg/apis/crunchydata.com/v1/common.go | 2 +- pkg/apis/crunchydata.com/v1/common_test.go | 2 +- pkg/apis/crunchydata.com/v1/doc.go | 2 +- pkg/apis/crunchydata.com/v1/errors.go | 2 +- pkg/apis/crunchydata.com/v1/policy.go | 2 +- pkg/apis/crunchydata.com/v1/register.go | 2 +- pkg/apis/crunchydata.com/v1/replica.go | 2 +- pkg/apis/crunchydata.com/v1/task.go | 2 +- pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go | 2 +- pkg/apiservermsgs/backrestmsgs.go | 2 +- pkg/apiservermsgs/catmsgs.go | 2 +- pkg/apiservermsgs/clustermsgs.go | 2 +- pkg/apiservermsgs/common.go | 2 +- pkg/apiservermsgs/configmsgs.go | 2 +- pkg/apiservermsgs/dfmsgs.go | 2 +- pkg/apiservermsgs/failovermsgs.go | 2 +- pkg/apiservermsgs/labelmsgs.go | 2 +- pkg/apiservermsgs/namespacemsgs.go | 2 +- pkg/apiservermsgs/pgadminmsgs.go | 2 +- pkg/apiservermsgs/pgbouncermsgs.go | 2 +- pkg/apiservermsgs/pgdumpmsgs.go | 2 +- pkg/apiservermsgs/pgorolemsgs.go | 2 +- pkg/apiservermsgs/pgousermsgs.go | 2 +- pkg/apiservermsgs/policymsgs.go | 2 +- pkg/apiservermsgs/pvcmsgs.go | 2 +- pkg/apiservermsgs/reloadmsgs.go | 2 +- pkg/apiservermsgs/restartmsgs.go | 2 +- pkg/apiservermsgs/schedulemsgs.go | 2 +- pkg/apiservermsgs/statusmsgs.go | 2 +- pkg/apiservermsgs/upgrademsgs.go | 2 +- pkg/apiservermsgs/usermsgs.go | 2 +- pkg/apiservermsgs/versionmsgs.go | 2 +- pkg/apiservermsgs/watchmsgs.go | 2 +- pkg/apiservermsgs/workflowmsgs.go | 2 +- pkg/events/eventing.go | 2 +- pkg/events/eventtype.go | 2 +- pkg/events/pgoeventtype.go | 2 +- pkg/generated/clientset/versioned/clientset.go | 2 +- pkg/generated/clientset/versioned/doc.go | 2 +- pkg/generated/clientset/versioned/fake/clientset_generated.go | 2 +- pkg/generated/clientset/versioned/fake/doc.go | 2 +- pkg/generated/clientset/versioned/fake/register.go | 2 +- pkg/generated/clientset/versioned/scheme/doc.go | 2 +- pkg/generated/clientset/versioned/scheme/register.go | 2 +- .../typed/crunchydata.com/v1/crunchydata.com_client.go | 2 +- .../clientset/versioned/typed/crunchydata.com/v1/doc.go | 2 +- .../clientset/versioned/typed/crunchydata.com/v1/fake/doc.go | 2 +- .../crunchydata.com/v1/fake/fake_crunchydata.com_client.go | 2 +- .../versioned/typed/crunchydata.com/v1/fake/fake_pgcluster.go | 2 +- .../versioned/typed/crunchydata.com/v1/fake/fake_pgpolicy.go | 2 +- .../versioned/typed/crunchydata.com/v1/fake/fake_pgreplica.go | 2 +- .../versioned/typed/crunchydata.com/v1/fake/fake_pgtask.go | 2 +- .../versioned/typed/crunchydata.com/v1/generated_expansion.go | 2 +- .../clientset/versioned/typed/crunchydata.com/v1/pgcluster.go | 2 +- .../clientset/versioned/typed/crunchydata.com/v1/pgpolicy.go | 2 +- .../clientset/versioned/typed/crunchydata.com/v1/pgreplica.go | 2 +- .../clientset/versioned/typed/crunchydata.com/v1/pgtask.go | 2 +- .../informers/externalversions/crunchydata.com/interface.go | 2 +- .../informers/externalversions/crunchydata.com/v1/interface.go | 2 +- .../informers/externalversions/crunchydata.com/v1/pgcluster.go | 2 +- .../informers/externalversions/crunchydata.com/v1/pgpolicy.go | 2 +- .../informers/externalversions/crunchydata.com/v1/pgreplica.go | 2 +- .../informers/externalversions/crunchydata.com/v1/pgtask.go | 2 +- pkg/generated/informers/externalversions/factory.go | 2 +- pkg/generated/informers/externalversions/generic.go | 2 +- .../externalversions/internalinterfaces/factory_interfaces.go | 2 +- pkg/generated/listers/crunchydata.com/v1/expansion_generated.go | 2 +- pkg/generated/listers/crunchydata.com/v1/pgcluster.go | 2 +- pkg/generated/listers/crunchydata.com/v1/pgpolicy.go | 2 +- pkg/generated/listers/crunchydata.com/v1/pgreplica.go | 2 +- pkg/generated/listers/crunchydata.com/v1/pgtask.go | 2 +- pv/create-pv-nfs-label.sh | 2 +- pv/create-pv-nfs-legacy.sh | 2 +- pv/create-pv-nfs.sh | 2 +- pv/create-pv.sh | 2 +- pv/delete-pv.sh | 2 +- testing/pgo_cli/cluster_annotation_test.go | 2 +- testing/pgo_cli/cluster_backup_test.go | 2 +- testing/pgo_cli/cluster_cat_test.go | 2 +- testing/pgo_cli/cluster_create_test.go | 2 +- testing/pgo_cli/cluster_delete_test.go | 2 +- testing/pgo_cli/cluster_df_test.go | 2 +- testing/pgo_cli/cluster_failover_test.go | 2 +- testing/pgo_cli/cluster_label_test.go | 2 +- testing/pgo_cli/cluster_pgbouncer_test.go | 2 +- testing/pgo_cli/cluster_policy_test.go | 2 +- testing/pgo_cli/cluster_pvc_test.go | 2 +- testing/pgo_cli/cluster_reload_test.go | 2 +- testing/pgo_cli/cluster_restart_test.go | 2 +- testing/pgo_cli/cluster_scale_test.go | 2 +- testing/pgo_cli/cluster_scaledown_test.go | 2 +- testing/pgo_cli/cluster_test_test.go | 2 +- testing/pgo_cli/cluster_user_test.go | 2 +- testing/pgo_cli/operator_namespace_test.go | 2 +- testing/pgo_cli/operator_rbac_test.go | 2 +- testing/pgo_cli/operator_test.go | 2 +- testing/pgo_cli/suite_helpers_test.go | 2 +- testing/pgo_cli/suite_pgo_cmd_test.go | 2 +- testing/pgo_cli/suite_test.go | 2 +- 403 files changed, 403 insertions(+), 403 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 79e1438a7d..8ce5664373 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -176,7 +176,7 @@ END OF TERMS AND CONDITIONS - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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/bin/check-deps.sh b/bin/check-deps.sh index 819756afa8..5d9f117469 100755 --- a/bin/check-deps.sh +++ b/bin/check-deps.sh @@ -1,6 +1,6 @@ #!/bin/bash -e -# Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2020 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/crunchy-postgres-exporter/common_lib.sh b/bin/crunchy-postgres-exporter/common_lib.sh index 5dd828322c..720acb4468 100755 --- a/bin/crunchy-postgres-exporter/common_lib.sh +++ b/bin/crunchy-postgres-exporter/common_lib.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/crunchy-postgres-exporter/start.sh b/bin/crunchy-postgres-exporter/start.sh index 1ea756e95b..58c8247d14 100755 --- a/bin/crunchy-postgres-exporter/start.sh +++ b/bin/crunchy-postgres-exporter/start.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/get-deps.sh b/bin/get-deps.sh index 2108ef5231..fb85dd4b2c 100755 --- a/bin/get-deps.sh +++ b/bin/get-deps.sh @@ -1,6 +1,6 @@ #!/bin/bash -e -# Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/get-pgmonitor.sh b/bin/get-pgmonitor.sh index 5bf97d1475..6d717bd20f 100755 --- a/bin/get-pgmonitor.sh +++ b/bin/get-pgmonitor.sh @@ -1,6 +1,6 @@ #!/bin/bash -e -# Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/license_aggregator.sh b/bin/license_aggregator.sh index e4ee6d3123..6ba6f46cad 100755 --- a/bin/license_aggregator.sh +++ b/bin/license_aggregator.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2021 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2021 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/pgo-event/pgo-event.sh b/bin/pgo-event/pgo-event.sh index 7e16d5f3f1..b56b0a775b 100755 --- a/bin/pgo-event/pgo-event.sh +++ b/bin/pgo-event/pgo-event.sh @@ -1,6 +1,6 @@ #!/bin/bash -x -# Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/pgo-rmdata/start.sh b/bin/pgo-rmdata/start.sh index bf9dd9f9c2..7fca61c27d 100755 --- a/bin/pgo-rmdata/start.sh +++ b/bin/pgo-rmdata/start.sh @@ -1,6 +1,6 @@ #!/bin/bash -x -# Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/pgo-scheduler/start.sh b/bin/pgo-scheduler/start.sh index 758a25bbd3..96420fa9aa 100755 --- a/bin/pgo-scheduler/start.sh +++ b/bin/pgo-scheduler/start.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/pgo-sqlrunner/start.sh b/bin/pgo-sqlrunner/start.sh index 8a272855f1..9cbe715ad0 100755 --- a/bin/pgo-sqlrunner/start.sh +++ b/bin/pgo-sqlrunner/start.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/pre-pull-crunchy-containers.sh b/bin/pre-pull-crunchy-containers.sh index 2726b2e02d..2fa49a08f2 100755 --- a/bin/pre-pull-crunchy-containers.sh +++ b/bin/pre-pull-crunchy-containers.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/pull-from-gcr.sh b/bin/pull-from-gcr.sh index d0627dc343..d67feed3ca 100755 --- a/bin/pull-from-gcr.sh +++ b/bin/pull-from-gcr.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/push-ccp-to-gcr.sh b/bin/push-ccp-to-gcr.sh index a92f74dad5..11f701a87e 100755 --- a/bin/push-ccp-to-gcr.sh +++ b/bin/push-ccp-to-gcr.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/push-to-gcr.sh b/bin/push-to-gcr.sh index 7bfff6b5bb..0a50c56dc6 100755 --- a/bin/push-to-gcr.sh +++ b/bin/push-to-gcr.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/uid_daemon.sh b/bin/uid_daemon.sh index 58f8fd7505..6f32959b2f 100755 --- a/bin/uid_daemon.sh +++ b/bin/uid_daemon.sh @@ -1,6 +1,6 @@ #!/usr/bin/bash -# Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2020 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/bin/upgrade-secret.sh b/bin/upgrade-secret.sh index b52b751727..2c364389d9 100755 --- a/bin/upgrade-secret.sh +++ b/bin/upgrade-secret.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/cmd/apiserver/main.go b/cmd/apiserver/main.go index df97046cb7..58a9127be7 100644 --- a/cmd/apiserver/main.go +++ b/cmd/apiserver/main.go @@ -1,7 +1,7 @@ package main /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-rmdata/main.go b/cmd/pgo-rmdata/main.go index 13a9b19d94..881591e1e6 100644 --- a/cmd/pgo-rmdata/main.go +++ b/cmd/pgo-rmdata/main.go @@ -1,7 +1,7 @@ package main /* -Copyright 2019 - 2022 Crunchy Data +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-rmdata/process.go b/cmd/pgo-rmdata/process.go index c17e34dd17..e3b70f4a19 100644 --- a/cmd/pgo-rmdata/process.go +++ b/cmd/pgo-rmdata/process.go @@ -1,7 +1,7 @@ package main /* -Copyright 2019 - 2022 Crunchy Data +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-rmdata/types.go b/cmd/pgo-rmdata/types.go index a6055d3948..d0664eb2c5 100644 --- a/cmd/pgo-rmdata/types.go +++ b/cmd/pgo-rmdata/types.go @@ -1,7 +1,7 @@ package main /* -Copyright 2019 - 2022 Crunchy Data +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/main.go b/cmd/pgo-scheduler/main.go index 0e7268a2c2..89cbbb65dc 100644 --- a/cmd/pgo-scheduler/main.go +++ b/cmd/pgo-scheduler/main.go @@ -1,7 +1,7 @@ package main /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/scheduler/configmapcontroller.go b/cmd/pgo-scheduler/scheduler/configmapcontroller.go index e3c6444710..30a72db2d0 100644 --- a/cmd/pgo-scheduler/scheduler/configmapcontroller.go +++ b/cmd/pgo-scheduler/scheduler/configmapcontroller.go @@ -1,7 +1,7 @@ package scheduler /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/scheduler/controllermanager.go b/cmd/pgo-scheduler/scheduler/controllermanager.go index 11fc741242..84fb34fcc8 100644 --- a/cmd/pgo-scheduler/scheduler/controllermanager.go +++ b/cmd/pgo-scheduler/scheduler/controllermanager.go @@ -1,7 +1,7 @@ package scheduler /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/scheduler/pgbackrest.go b/cmd/pgo-scheduler/scheduler/pgbackrest.go index ba34428abf..77b390aa55 100644 --- a/cmd/pgo-scheduler/scheduler/pgbackrest.go +++ b/cmd/pgo-scheduler/scheduler/pgbackrest.go @@ -1,7 +1,7 @@ package scheduler /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/scheduler/policy.go b/cmd/pgo-scheduler/scheduler/policy.go index 114a3c6c4e..c170a222db 100644 --- a/cmd/pgo-scheduler/scheduler/policy.go +++ b/cmd/pgo-scheduler/scheduler/policy.go @@ -1,7 +1,7 @@ package scheduler /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/scheduler/scheduler.go b/cmd/pgo-scheduler/scheduler/scheduler.go index 554fd0bca2..0887985554 100644 --- a/cmd/pgo-scheduler/scheduler/scheduler.go +++ b/cmd/pgo-scheduler/scheduler/scheduler.go @@ -1,7 +1,7 @@ package scheduler /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/scheduler/tasks.go b/cmd/pgo-scheduler/scheduler/tasks.go index a79a60ba7f..30560aff8b 100644 --- a/cmd/pgo-scheduler/scheduler/tasks.go +++ b/cmd/pgo-scheduler/scheduler/tasks.go @@ -1,7 +1,7 @@ package scheduler /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/scheduler/types.go b/cmd/pgo-scheduler/scheduler/types.go index 8850e3ebc7..67fad2f8c2 100644 --- a/cmd/pgo-scheduler/scheduler/types.go +++ b/cmd/pgo-scheduler/scheduler/types.go @@ -1,7 +1,7 @@ package scheduler /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/scheduler/validate.go b/cmd/pgo-scheduler/scheduler/validate.go index 660b6f381d..8af1ee8b09 100644 --- a/cmd/pgo-scheduler/scheduler/validate.go +++ b/cmd/pgo-scheduler/scheduler/validate.go @@ -1,7 +1,7 @@ package scheduler /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo-scheduler/scheduler/validate_test.go b/cmd/pgo-scheduler/scheduler/validate_test.go index ccf9b24334..176bcf3e22 100644 --- a/cmd/pgo-scheduler/scheduler/validate_test.go +++ b/cmd/pgo-scheduler/scheduler/validate_test.go @@ -1,7 +1,7 @@ package scheduler /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/backrest.go b/cmd/pgo/api/backrest.go index 0a36376a7d..ad8a8d4505 100644 --- a/cmd/pgo/api/backrest.go +++ b/cmd/pgo/api/backrest.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/cat.go b/cmd/pgo/api/cat.go index d6abdffe75..9e159844e5 100644 --- a/cmd/pgo/api/cat.go +++ b/cmd/pgo/api/cat.go @@ -1,7 +1,7 @@ package api /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/cluster.go b/cmd/pgo/api/cluster.go index 6471ccc5cd..817d5a884e 100644 --- a/cmd/pgo/api/cluster.go +++ b/cmd/pgo/api/cluster.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/common.go b/cmd/pgo/api/common.go index c990bcc93c..a17299c37d 100644 --- a/cmd/pgo/api/common.go +++ b/cmd/pgo/api/common.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/config.go b/cmd/pgo/api/config.go index 9e5370ec5d..1915c3b0d3 100644 --- a/cmd/pgo/api/config.go +++ b/cmd/pgo/api/config.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/df.go b/cmd/pgo/api/df.go index 9a7ca77ecf..0851118d9b 100644 --- a/cmd/pgo/api/df.go +++ b/cmd/pgo/api/df.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/failover.go b/cmd/pgo/api/failover.go index 668e74d517..41bac507e1 100644 --- a/cmd/pgo/api/failover.go +++ b/cmd/pgo/api/failover.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/label.go b/cmd/pgo/api/label.go index b48aabbbc2..303710c7bf 100644 --- a/cmd/pgo/api/label.go +++ b/cmd/pgo/api/label.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/namespace.go b/cmd/pgo/api/namespace.go index 939e7a7608..96dc49617a 100644 --- a/cmd/pgo/api/namespace.go +++ b/cmd/pgo/api/namespace.go @@ -1,7 +1,7 @@ package api /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/pgadmin.go b/cmd/pgo/api/pgadmin.go index cb015f5fef..279420a612 100644 --- a/cmd/pgo/api/pgadmin.go +++ b/cmd/pgo/api/pgadmin.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/pgbouncer.go b/cmd/pgo/api/pgbouncer.go index d58ab3f255..c227956241 100644 --- a/cmd/pgo/api/pgbouncer.go +++ b/cmd/pgo/api/pgbouncer.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/pgdump.go b/cmd/pgo/api/pgdump.go index 1fc7d4b359..18ae1fa903 100644 --- a/cmd/pgo/api/pgdump.go +++ b/cmd/pgo/api/pgdump.go @@ -1,7 +1,7 @@ package api /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/pgorole.go b/cmd/pgo/api/pgorole.go index 8f28928432..4154145ecd 100644 --- a/cmd/pgo/api/pgorole.go +++ b/cmd/pgo/api/pgorole.go @@ -1,7 +1,7 @@ package api /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/pgouser.go b/cmd/pgo/api/pgouser.go index 06904ecde9..82b0bd9ffa 100644 --- a/cmd/pgo/api/pgouser.go +++ b/cmd/pgo/api/pgouser.go @@ -1,7 +1,7 @@ package api /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/policy.go b/cmd/pgo/api/policy.go index 17196b5c9f..54ae74060e 100644 --- a/cmd/pgo/api/policy.go +++ b/cmd/pgo/api/policy.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/pvc.go b/cmd/pgo/api/pvc.go index fffd7bbe5d..6b385efd96 100644 --- a/cmd/pgo/api/pvc.go +++ b/cmd/pgo/api/pvc.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/reload.go b/cmd/pgo/api/reload.go index c39030f289..81368841ce 100644 --- a/cmd/pgo/api/reload.go +++ b/cmd/pgo/api/reload.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/restart.go b/cmd/pgo/api/restart.go index 8a999f032f..69f1ddb781 100644 --- a/cmd/pgo/api/restart.go +++ b/cmd/pgo/api/restart.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/restore.go b/cmd/pgo/api/restore.go index e62391ff43..67c9dfc073 100644 --- a/cmd/pgo/api/restore.go +++ b/cmd/pgo/api/restore.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/restoreDump.go b/cmd/pgo/api/restoreDump.go index ae78d2a18b..8cff5f35f1 100644 --- a/cmd/pgo/api/restoreDump.go +++ b/cmd/pgo/api/restoreDump.go @@ -1,7 +1,7 @@ package api /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/scale.go b/cmd/pgo/api/scale.go index 17b41ddfff..8cdd8a04cc 100644 --- a/cmd/pgo/api/scale.go +++ b/cmd/pgo/api/scale.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/scaledown.go b/cmd/pgo/api/scaledown.go index e7ce4fcbc1..8035e3c7d9 100644 --- a/cmd/pgo/api/scaledown.go +++ b/cmd/pgo/api/scaledown.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/schedule.go b/cmd/pgo/api/schedule.go index 3967a7f949..ec86e094be 100644 --- a/cmd/pgo/api/schedule.go +++ b/cmd/pgo/api/schedule.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/status.go b/cmd/pgo/api/status.go index 6a9d1712fd..b266bcfc64 100644 --- a/cmd/pgo/api/status.go +++ b/cmd/pgo/api/status.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/test.go b/cmd/pgo/api/test.go index 3270695d8f..dd42619a33 100644 --- a/cmd/pgo/api/test.go +++ b/cmd/pgo/api/test.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/upgrade.go b/cmd/pgo/api/upgrade.go index e273defcb3..8b03c09311 100644 --- a/cmd/pgo/api/upgrade.go +++ b/cmd/pgo/api/upgrade.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/user.go b/cmd/pgo/api/user.go index 56d1928648..3b848f3028 100644 --- a/cmd/pgo/api/user.go +++ b/cmd/pgo/api/user.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/version.go b/cmd/pgo/api/version.go index e537a335a3..f5a5c22b68 100644 --- a/cmd/pgo/api/version.go +++ b/cmd/pgo/api/version.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/api/workflow.go b/cmd/pgo/api/workflow.go index 212a5bc3cb..da1997e365 100644 --- a/cmd/pgo/api/workflow.go +++ b/cmd/pgo/api/workflow.go @@ -1,7 +1,7 @@ package api /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/auth.go b/cmd/pgo/cmd/auth.go index 1b0413e6c8..341c8ff237 100644 --- a/cmd/pgo/cmd/auth.go +++ b/cmd/pgo/cmd/auth.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/backrest.go b/cmd/pgo/cmd/backrest.go index aace9b79f7..04a2c29c95 100644 --- a/cmd/pgo/cmd/backrest.go +++ b/cmd/pgo/cmd/backrest.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/backup.go b/cmd/pgo/cmd/backup.go index 3eece26fa5..23306c7544 100644 --- a/cmd/pgo/cmd/backup.go +++ b/cmd/pgo/cmd/backup.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/cat.go b/cmd/pgo/cmd/cat.go index fb3007c2dc..a7a78deb77 100644 --- a/cmd/pgo/cmd/cat.go +++ b/cmd/pgo/cmd/cat.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/cluster.go b/cmd/pgo/cmd/cluster.go index 31ecda6faf..106c0eb818 100644 --- a/cmd/pgo/cmd/cluster.go +++ b/cmd/pgo/cmd/cluster.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/common.go b/cmd/pgo/cmd/common.go index cdd4f8e97b..c0ec21439c 100644 --- a/cmd/pgo/cmd/common.go +++ b/cmd/pgo/cmd/common.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/config.go b/cmd/pgo/cmd/config.go index a9edd4e024..78b4086077 100644 --- a/cmd/pgo/cmd/config.go +++ b/cmd/pgo/cmd/config.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/create.go b/cmd/pgo/cmd/create.go index 94b8e01d8b..a307c2b99a 100644 --- a/cmd/pgo/cmd/create.go +++ b/cmd/pgo/cmd/create.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/delete.go b/cmd/pgo/cmd/delete.go index 59417f78ff..7750feca4e 100644 --- a/cmd/pgo/cmd/delete.go +++ b/cmd/pgo/cmd/delete.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/df.go b/cmd/pgo/cmd/df.go index 4247eb040b..99c6b4c4f3 100644 --- a/cmd/pgo/cmd/df.go +++ b/cmd/pgo/cmd/df.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/failover.go b/cmd/pgo/cmd/failover.go index f791e208a9..239987d064 100644 --- a/cmd/pgo/cmd/failover.go +++ b/cmd/pgo/cmd/failover.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/flags.go b/cmd/pgo/cmd/flags.go index c6ced86ca2..643af26229 100644 --- a/cmd/pgo/cmd/flags.go +++ b/cmd/pgo/cmd/flags.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/label.go b/cmd/pgo/cmd/label.go index de3f06298a..b1a4d1e559 100644 --- a/cmd/pgo/cmd/label.go +++ b/cmd/pgo/cmd/label.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/namespace.go b/cmd/pgo/cmd/namespace.go index 9f38938e63..32212baa9d 100644 --- a/cmd/pgo/cmd/namespace.go +++ b/cmd/pgo/cmd/namespace.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/pgadmin.go b/cmd/pgo/cmd/pgadmin.go index 7e22612c03..3619ca6ff8 100644 --- a/cmd/pgo/cmd/pgadmin.go +++ b/cmd/pgo/cmd/pgadmin.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/pgbouncer.go b/cmd/pgo/cmd/pgbouncer.go index 7053b390d6..16bf9d726c 100644 --- a/cmd/pgo/cmd/pgbouncer.go +++ b/cmd/pgo/cmd/pgbouncer.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/pgdump.go b/cmd/pgo/cmd/pgdump.go index 26342fcb83..92707c88bf 100644 --- a/cmd/pgo/cmd/pgdump.go +++ b/cmd/pgo/cmd/pgdump.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/pgorole.go b/cmd/pgo/cmd/pgorole.go index 45b08d7623..2ac20cd730 100644 --- a/cmd/pgo/cmd/pgorole.go +++ b/cmd/pgo/cmd/pgorole.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/pgouser.go b/cmd/pgo/cmd/pgouser.go index 191467f3c1..086d4c7e52 100644 --- a/cmd/pgo/cmd/pgouser.go +++ b/cmd/pgo/cmd/pgouser.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/policy.go b/cmd/pgo/cmd/policy.go index ef43016893..3064e772f5 100644 --- a/cmd/pgo/cmd/policy.go +++ b/cmd/pgo/cmd/policy.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/pvc.go b/cmd/pgo/cmd/pvc.go index d9952a6e4a..08644de2b5 100644 --- a/cmd/pgo/cmd/pvc.go +++ b/cmd/pgo/cmd/pvc.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/reload.go b/cmd/pgo/cmd/reload.go index e811185f1f..20978502b6 100644 --- a/cmd/pgo/cmd/reload.go +++ b/cmd/pgo/cmd/reload.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/restart.go b/cmd/pgo/cmd/restart.go index 7ea29d8405..0f239af6c1 100644 --- a/cmd/pgo/cmd/restart.go +++ b/cmd/pgo/cmd/restart.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/restore.go b/cmd/pgo/cmd/restore.go index 692e19fea8..71b80970df 100644 --- a/cmd/pgo/cmd/restore.go +++ b/cmd/pgo/cmd/restore.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/root.go b/cmd/pgo/cmd/root.go index e09994ce6a..ffa79db011 100644 --- a/cmd/pgo/cmd/root.go +++ b/cmd/pgo/cmd/root.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/scale.go b/cmd/pgo/cmd/scale.go index e92e0c605b..cbedeb8003 100644 --- a/cmd/pgo/cmd/scale.go +++ b/cmd/pgo/cmd/scale.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/scaledown.go b/cmd/pgo/cmd/scaledown.go index 47f4b7e902..6ec996fa34 100644 --- a/cmd/pgo/cmd/scaledown.go +++ b/cmd/pgo/cmd/scaledown.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/schedule.go b/cmd/pgo/cmd/schedule.go index 20717ff229..df5d51ea1c 100644 --- a/cmd/pgo/cmd/schedule.go +++ b/cmd/pgo/cmd/schedule.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/show.go b/cmd/pgo/cmd/show.go index c23024761d..6f46aed52c 100644 --- a/cmd/pgo/cmd/show.go +++ b/cmd/pgo/cmd/show.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/status.go b/cmd/pgo/cmd/status.go index 75e3ff019f..562604b18f 100644 --- a/cmd/pgo/cmd/status.go +++ b/cmd/pgo/cmd/status.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/test.go b/cmd/pgo/cmd/test.go index bcaf62c316..f6503673a9 100644 --- a/cmd/pgo/cmd/test.go +++ b/cmd/pgo/cmd/test.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/update.go b/cmd/pgo/cmd/update.go index 5225a10809..9d6544a6a1 100644 --- a/cmd/pgo/cmd/update.go +++ b/cmd/pgo/cmd/update.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/upgrade.go b/cmd/pgo/cmd/upgrade.go index 0571f813a6..969e80956c 100644 --- a/cmd/pgo/cmd/upgrade.go +++ b/cmd/pgo/cmd/upgrade.go @@ -2,7 +2,7 @@ package cmd /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/user.go b/cmd/pgo/cmd/user.go index d42c7004c8..b3b005016c 100644 --- a/cmd/pgo/cmd/user.go +++ b/cmd/pgo/cmd/user.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/version.go b/cmd/pgo/cmd/version.go index 39482eb64f..b7c45a2c1c 100644 --- a/cmd/pgo/cmd/version.go +++ b/cmd/pgo/cmd/version.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/watch.go b/cmd/pgo/cmd/watch.go index 43f7f52eb4..3ddff23f93 100644 --- a/cmd/pgo/cmd/watch.go +++ b/cmd/pgo/cmd/watch.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/cmd/workflow.go b/cmd/pgo/cmd/workflow.go index 309df61b5b..944544b732 100644 --- a/cmd/pgo/cmd/workflow.go +++ b/cmd/pgo/cmd/workflow.go @@ -1,7 +1,7 @@ package cmd /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/generatedocs.go b/cmd/pgo/generatedocs.go index 2131ccfc72..177577c89d 100644 --- a/cmd/pgo/generatedocs.go +++ b/cmd/pgo/generatedocs.go @@ -3,7 +3,7 @@ package main /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/main.go b/cmd/pgo/main.go index 8c525bd72a..c3f1c3ae45 100644 --- a/cmd/pgo/main.go +++ b/cmd/pgo/main.go @@ -1,7 +1,7 @@ package main /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/util/confirmation.go b/cmd/pgo/util/confirmation.go index 732f4de70a..a79747a735 100644 --- a/cmd/pgo/util/confirmation.go +++ b/cmd/pgo/util/confirmation.go @@ -1,7 +1,7 @@ package util /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/util/pad.go b/cmd/pgo/util/pad.go index 8c4affbbdf..a68dc7852d 100644 --- a/cmd/pgo/util/pad.go +++ b/cmd/pgo/util/pad.go @@ -1,7 +1,7 @@ package util /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/pgo/util/validation.go b/cmd/pgo/util/validation.go index 4b169a4d24..1cc1de167b 100644 --- a/cmd/pgo/util/validation.go +++ b/cmd/pgo/util/validation.go @@ -1,7 +1,7 @@ package util /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/postgres-operator/main.go b/cmd/postgres-operator/main.go index f103d1b235..8dda0c0520 100644 --- a/cmd/postgres-operator/main.go +++ b/cmd/postgres-operator/main.go @@ -1,7 +1,7 @@ package main /* -Copyright 2017 - 2022 Crunchy Data +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/cmd/postgres-operator/open_telemetry.go b/cmd/postgres-operator/open_telemetry.go index 44b3880e1f..7d5498bbf6 100644 --- a/cmd/postgres-operator/open_telemetry.go +++ b/cmd/postgres-operator/open_telemetry.go @@ -1,7 +1,7 @@ package main /* -Copyright 2020 - 2022 Crunchy Data +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/deploy/add-targeted-namespace-reconcile-rbac.sh b/deploy/add-targeted-namespace-reconcile-rbac.sh index 66eddb96a0..420eb343d6 100755 --- a/deploy/add-targeted-namespace-reconcile-rbac.sh +++ b/deploy/add-targeted-namespace-reconcile-rbac.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2020 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/add-targeted-namespace.sh b/deploy/add-targeted-namespace.sh index 9d9b0e58d2..80163fa2c3 100755 --- a/deploy/add-targeted-namespace.sh +++ b/deploy/add-targeted-namespace.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/cleannamespaces.sh b/deploy/cleannamespaces.sh index 3ad11e8766..983f92f1b0 100755 --- a/deploy/cleannamespaces.sh +++ b/deploy/cleannamespaces.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/cleanup-rbac.sh b/deploy/cleanup-rbac.sh index 61e07e13b4..c6cd7c8ccc 100755 --- a/deploy/cleanup-rbac.sh +++ b/deploy/cleanup-rbac.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/cleanup.sh b/deploy/cleanup.sh index 0068cb0fd7..d779d68393 100755 --- a/deploy/cleanup.sh +++ b/deploy/cleanup.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/deploy.sh b/deploy/deploy.sh index e2abc5c25c..30fbf32b37 100755 --- a/deploy/deploy.sh +++ b/deploy/deploy.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/gen-api-keys.sh b/deploy/gen-api-keys.sh index 4c985908c2..de7f67fa98 100755 --- a/deploy/gen-api-keys.sh +++ b/deploy/gen-api-keys.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/install-bootstrap-creds.sh b/deploy/install-bootstrap-creds.sh index 28c249b42a..9b16665a58 100755 --- a/deploy/install-bootstrap-creds.sh +++ b/deploy/install-bootstrap-creds.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/install-rbac.sh b/deploy/install-rbac.sh index 9c1b900ca7..f23f8b784f 100755 --- a/deploy/install-rbac.sh +++ b/deploy/install-rbac.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/remove-crd.sh b/deploy/remove-crd.sh index 3d2182c739..17a56c1872 100755 --- a/deploy/remove-crd.sh +++ b/deploy/remove-crd.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/setupnamespaces.sh b/deploy/setupnamespaces.sh index 50f5e874a6..2cb8c0dbea 100755 --- a/deploy/setupnamespaces.sh +++ b/deploy/setupnamespaces.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/show-crd.sh b/deploy/show-crd.sh index 6af0001e7c..09e6690002 100755 --- a/deploy/show-crd.sh +++ b/deploy/show-crd.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/upgrade-creds.sh b/deploy/upgrade-creds.sh index 1b1024202a..df4740dfbd 100755 --- a/deploy/upgrade-creds.sh +++ b/deploy/upgrade-creds.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/deploy/upgrade-pgo.sh b/deploy/upgrade-pgo.sh index 9ddbfd5910..38b4e68832 100755 --- a/deploy/upgrade-pgo.sh +++ b/deploy/upgrade-pgo.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2020 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/docs/layouts/partials/flex/body-aftercontent.html b/docs/layouts/partials/flex/body-aftercontent.html index 1ec038ea12..6637c4c6be 100644 --- a/docs/layouts/partials/flex/body-aftercontent.html +++ b/docs/layouts/partials/flex/body-aftercontent.html @@ -35,7 +35,7 @@
-

© 2017 - 2022 Crunchy Data Solutions, Inc.

+

© 2017 - 2023 Crunchy Data Solutions, Inc.

diff --git a/examples/create-by-resource/run.sh b/examples/create-by-resource/run.sh index 1f174d844c..bca4f09edd 100755 --- a/examples/create-by-resource/run.sh +++ b/examples/create-by-resource/run.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2019 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/examples/custom-config/create.sh b/examples/custom-config/create.sh index 519ab0433e..e502bdbada 100755 --- a/examples/custom-config/create.sh +++ b/examples/custom-config/create.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/examples/custom-config/setup.sql b/examples/custom-config/setup.sql index ca7510ff93..01942a034e 100644 --- a/examples/custom-config/setup.sql +++ b/examples/custom-config/setup.sql @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + * Copyright 2017 - 2023 Crunchy Data Solutions, Inc. * 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 diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt index 22523257ce..82f4fd7c7a 100644 --- a/hack/boilerplate.go.txt +++ b/hack/boilerplate.go.txt @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/hack/config_sync.sh b/hack/config_sync.sh index b7a7b95b95..7a8cb042b1 100755 --- a/hack/config_sync.sh +++ b/hack/config_sync.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2020 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index e53bdb9eda..ca0a09a112 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2020 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/installers/ansible/roles/pgo-operator/templates/add-targeted-namespace.sh.j2 b/installers/ansible/roles/pgo-operator/templates/add-targeted-namespace.sh.j2 index b8f2ab2e9f..ccaa310903 100644 --- a/installers/ansible/roles/pgo-operator/templates/add-targeted-namespace.sh.j2 +++ b/installers/ansible/roles/pgo-operator/templates/add-targeted-namespace.sh.j2 @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/installers/image/bin/pgo-deploy.sh b/installers/image/bin/pgo-deploy.sh index 088d37c5de..11c81eeae2 100755 --- a/installers/image/bin/pgo-deploy.sh +++ b/installers/image/bin/pgo-deploy.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2020 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/installers/kubectl/client-setup.sh b/installers/kubectl/client-setup.sh index dc60c69b95..7eae3218d7 100755 --- a/installers/kubectl/client-setup.sh +++ b/installers/kubectl/client-setup.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2020 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/internal/apiserver/backrestservice/backrestimpl.go b/internal/apiserver/backrestservice/backrestimpl.go index d3344334a8..0d1072e7ef 100644 --- a/internal/apiserver/backrestservice/backrestimpl.go +++ b/internal/apiserver/backrestservice/backrestimpl.go @@ -1,7 +1,7 @@ package backrestservice /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/backrestservice/backrestservice.go b/internal/apiserver/backrestservice/backrestservice.go index 18b5aea9db..966146a772 100644 --- a/internal/apiserver/backrestservice/backrestservice.go +++ b/internal/apiserver/backrestservice/backrestservice.go @@ -1,7 +1,7 @@ package backrestservice /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/backupoptions/backupoptionsutil.go b/internal/apiserver/backupoptions/backupoptionsutil.go index ec4bac560c..304110a3a0 100644 --- a/internal/apiserver/backupoptions/backupoptionsutil.go +++ b/internal/apiserver/backupoptions/backupoptionsutil.go @@ -1,7 +1,7 @@ package backupoptions /* -Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/backupoptions/backupoptionsutil_test.go b/internal/apiserver/backupoptions/backupoptionsutil_test.go index 8a996b4591..df95262d50 100644 --- a/internal/apiserver/backupoptions/backupoptionsutil_test.go +++ b/internal/apiserver/backupoptions/backupoptionsutil_test.go @@ -1,7 +1,7 @@ package backupoptions /* -Copyright 2021 - 2022 Crunchy Data Solutions, Inc. +Copyright 2021 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/backupoptions/pgbackrestoptions.go b/internal/apiserver/backupoptions/pgbackrestoptions.go index 0a05f2d74c..fe88291cc2 100644 --- a/internal/apiserver/backupoptions/pgbackrestoptions.go +++ b/internal/apiserver/backupoptions/pgbackrestoptions.go @@ -1,7 +1,7 @@ package backupoptions /* -Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/backupoptions/pgdumpoptions.go b/internal/apiserver/backupoptions/pgdumpoptions.go index 405fc1926c..a1a7e155a4 100644 --- a/internal/apiserver/backupoptions/pgdumpoptions.go +++ b/internal/apiserver/backupoptions/pgdumpoptions.go @@ -1,7 +1,7 @@ package backupoptions /* -Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/catservice/catimpl.go b/internal/apiserver/catservice/catimpl.go index 2664da6edb..8f8bbc088c 100644 --- a/internal/apiserver/catservice/catimpl.go +++ b/internal/apiserver/catservice/catimpl.go @@ -1,7 +1,7 @@ package catservice /* -Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/catservice/catservice.go b/internal/apiserver/catservice/catservice.go index 1c3ea605d0..bdc2ce97d5 100644 --- a/internal/apiserver/catservice/catservice.go +++ b/internal/apiserver/catservice/catservice.go @@ -1,7 +1,7 @@ package catservice /* -Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/clusterservice/clusterimpl.go b/internal/apiserver/clusterservice/clusterimpl.go index 52eb4cc248..374344054e 100644 --- a/internal/apiserver/clusterservice/clusterimpl.go +++ b/internal/apiserver/clusterservice/clusterimpl.go @@ -1,7 +1,7 @@ package clusterservice /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/clusterservice/clusterservice.go b/internal/apiserver/clusterservice/clusterservice.go index 2d8ece09fe..9ab3c1c9eb 100644 --- a/internal/apiserver/clusterservice/clusterservice.go +++ b/internal/apiserver/clusterservice/clusterservice.go @@ -1,7 +1,7 @@ package clusterservice /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/clusterservice/scaleimpl.go b/internal/apiserver/clusterservice/scaleimpl.go index 9688d58bcd..2c98c02fef 100644 --- a/internal/apiserver/clusterservice/scaleimpl.go +++ b/internal/apiserver/clusterservice/scaleimpl.go @@ -1,7 +1,7 @@ package clusterservice /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/clusterservice/scaleservice.go b/internal/apiserver/clusterservice/scaleservice.go index 8e8aad311a..414e455623 100644 --- a/internal/apiserver/clusterservice/scaleservice.go +++ b/internal/apiserver/clusterservice/scaleservice.go @@ -1,7 +1,7 @@ package clusterservice /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/common.go b/internal/apiserver/common.go index 3f6154b937..37395cb8c8 100644 --- a/internal/apiserver/common.go +++ b/internal/apiserver/common.go @@ -1,7 +1,7 @@ package apiserver /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/common_test.go b/internal/apiserver/common_test.go index c0f788fede..8d217bbc9d 100644 --- a/internal/apiserver/common_test.go +++ b/internal/apiserver/common_test.go @@ -1,7 +1,7 @@ package apiserver /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/configservice/configimpl.go b/internal/apiserver/configservice/configimpl.go index 6f956479c0..1aa47a1f21 100644 --- a/internal/apiserver/configservice/configimpl.go +++ b/internal/apiserver/configservice/configimpl.go @@ -1,7 +1,7 @@ package configservice /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/configservice/configservice.go b/internal/apiserver/configservice/configservice.go index 370d514873..10a00dd4e5 100644 --- a/internal/apiserver/configservice/configservice.go +++ b/internal/apiserver/configservice/configservice.go @@ -1,7 +1,7 @@ package configservice /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/dfservice/dfimpl.go b/internal/apiserver/dfservice/dfimpl.go index f2b7525d66..bcbdfc68ad 100644 --- a/internal/apiserver/dfservice/dfimpl.go +++ b/internal/apiserver/dfservice/dfimpl.go @@ -1,7 +1,7 @@ package dfservice /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/dfservice/dfservice.go b/internal/apiserver/dfservice/dfservice.go index 4770d78b49..3a4e6e4bd3 100644 --- a/internal/apiserver/dfservice/dfservice.go +++ b/internal/apiserver/dfservice/dfservice.go @@ -1,7 +1,7 @@ package dfservice /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/failoverservice/failoverimpl.go b/internal/apiserver/failoverservice/failoverimpl.go index 643d8c9436..b6d45e456f 100644 --- a/internal/apiserver/failoverservice/failoverimpl.go +++ b/internal/apiserver/failoverservice/failoverimpl.go @@ -1,7 +1,7 @@ package failoverservice /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/failoverservice/failoverservice.go b/internal/apiserver/failoverservice/failoverservice.go index 64eb1476cf..66214bc071 100644 --- a/internal/apiserver/failoverservice/failoverservice.go +++ b/internal/apiserver/failoverservice/failoverservice.go @@ -1,7 +1,7 @@ package failoverservice /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/labelservice/labelimpl.go b/internal/apiserver/labelservice/labelimpl.go index c053aab613..7f86c89374 100644 --- a/internal/apiserver/labelservice/labelimpl.go +++ b/internal/apiserver/labelservice/labelimpl.go @@ -1,7 +1,7 @@ package labelservice /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/labelservice/labelservice.go b/internal/apiserver/labelservice/labelservice.go index 38bfb16a62..f56d3fe1aa 100644 --- a/internal/apiserver/labelservice/labelservice.go +++ b/internal/apiserver/labelservice/labelservice.go @@ -1,7 +1,7 @@ package labelservice /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/middleware.go b/internal/apiserver/middleware.go index 3977341a34..a234f36cb3 100644 --- a/internal/apiserver/middleware.go +++ b/internal/apiserver/middleware.go @@ -1,7 +1,7 @@ package apiserver /* -Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/namespaceservice/namespaceimpl.go b/internal/apiserver/namespaceservice/namespaceimpl.go index 7106fc3a0b..ac56c05671 100644 --- a/internal/apiserver/namespaceservice/namespaceimpl.go +++ b/internal/apiserver/namespaceservice/namespaceimpl.go @@ -1,7 +1,7 @@ package namespaceservice /* -Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/namespaceservice/namespaceservice.go b/internal/apiserver/namespaceservice/namespaceservice.go index 38c3835699..be5133b2d3 100644 --- a/internal/apiserver/namespaceservice/namespaceservice.go +++ b/internal/apiserver/namespaceservice/namespaceservice.go @@ -1,7 +1,7 @@ package namespaceservice /* -Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/perms.go b/internal/apiserver/perms.go index 9e692dc27f..b5bccc3403 100644 --- a/internal/apiserver/perms.go +++ b/internal/apiserver/perms.go @@ -1,7 +1,7 @@ package apiserver /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgadminservice/pgadminimpl.go b/internal/apiserver/pgadminservice/pgadminimpl.go index 2416c86556..cc2494269a 100644 --- a/internal/apiserver/pgadminservice/pgadminimpl.go +++ b/internal/apiserver/pgadminservice/pgadminimpl.go @@ -1,7 +1,7 @@ package pgadminservice /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgadminservice/pgadminservice.go b/internal/apiserver/pgadminservice/pgadminservice.go index 23fdcb2404..9c09cc884c 100644 --- a/internal/apiserver/pgadminservice/pgadminservice.go +++ b/internal/apiserver/pgadminservice/pgadminservice.go @@ -1,7 +1,7 @@ package pgadminservice /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgbouncerservice/pgbouncerimpl.go b/internal/apiserver/pgbouncerservice/pgbouncerimpl.go index 14c2f8f093..ae01a855dc 100644 --- a/internal/apiserver/pgbouncerservice/pgbouncerimpl.go +++ b/internal/apiserver/pgbouncerservice/pgbouncerimpl.go @@ -1,7 +1,7 @@ package pgbouncerservice /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgbouncerservice/pgbouncerservice.go b/internal/apiserver/pgbouncerservice/pgbouncerservice.go index 707a356e32..0d025d6e5b 100644 --- a/internal/apiserver/pgbouncerservice/pgbouncerservice.go +++ b/internal/apiserver/pgbouncerservice/pgbouncerservice.go @@ -1,7 +1,7 @@ package pgbouncerservice /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgdumpservice/pgdumpimpl.go b/internal/apiserver/pgdumpservice/pgdumpimpl.go index a2d5b2457e..ebe0e84c2a 100644 --- a/internal/apiserver/pgdumpservice/pgdumpimpl.go +++ b/internal/apiserver/pgdumpservice/pgdumpimpl.go @@ -1,7 +1,7 @@ package pgdumpservice /* -Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgdumpservice/pgdumpservice.go b/internal/apiserver/pgdumpservice/pgdumpservice.go index b3a3976a9a..5be602b811 100644 --- a/internal/apiserver/pgdumpservice/pgdumpservice.go +++ b/internal/apiserver/pgdumpservice/pgdumpservice.go @@ -1,7 +1,7 @@ package pgdumpservice /* -Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgoroleservice/pgoroleimpl.go b/internal/apiserver/pgoroleservice/pgoroleimpl.go index 6d945ab709..733d1b5301 100644 --- a/internal/apiserver/pgoroleservice/pgoroleimpl.go +++ b/internal/apiserver/pgoroleservice/pgoroleimpl.go @@ -1,7 +1,7 @@ package pgoroleservice /* -Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgoroleservice/pgoroleservice.go b/internal/apiserver/pgoroleservice/pgoroleservice.go index c53b9d56b4..6ba24d1b77 100644 --- a/internal/apiserver/pgoroleservice/pgoroleservice.go +++ b/internal/apiserver/pgoroleservice/pgoroleservice.go @@ -1,7 +1,7 @@ package pgoroleservice /* -Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgouserservice/pgouserimpl.go b/internal/apiserver/pgouserservice/pgouserimpl.go index e45376c403..c8c80043f3 100644 --- a/internal/apiserver/pgouserservice/pgouserimpl.go +++ b/internal/apiserver/pgouserservice/pgouserimpl.go @@ -1,7 +1,7 @@ package pgouserservice /* -Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pgouserservice/pgouserservice.go b/internal/apiserver/pgouserservice/pgouserservice.go index a28a9e871d..1f30da2a09 100644 --- a/internal/apiserver/pgouserservice/pgouserservice.go +++ b/internal/apiserver/pgouserservice/pgouserservice.go @@ -1,7 +1,7 @@ package pgouserservice /* -Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/policyservice/policyimpl.go b/internal/apiserver/policyservice/policyimpl.go index be49ef72f7..cfa796c49e 100644 --- a/internal/apiserver/policyservice/policyimpl.go +++ b/internal/apiserver/policyservice/policyimpl.go @@ -1,7 +1,7 @@ package policyservice /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/policyservice/policyservice.go b/internal/apiserver/policyservice/policyservice.go index 750bff651e..026228dc40 100644 --- a/internal/apiserver/policyservice/policyservice.go +++ b/internal/apiserver/policyservice/policyservice.go @@ -1,7 +1,7 @@ package policyservice /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pvcservice/pvcimpl.go b/internal/apiserver/pvcservice/pvcimpl.go index 81f2058139..2aa80e6e23 100644 --- a/internal/apiserver/pvcservice/pvcimpl.go +++ b/internal/apiserver/pvcservice/pvcimpl.go @@ -1,7 +1,7 @@ package pvcservice /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/pvcservice/pvcservice.go b/internal/apiserver/pvcservice/pvcservice.go index bf8b6b0440..5cbb939867 100644 --- a/internal/apiserver/pvcservice/pvcservice.go +++ b/internal/apiserver/pvcservice/pvcservice.go @@ -1,7 +1,7 @@ package pvcservice /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/reloadservice/reloadimpl.go b/internal/apiserver/reloadservice/reloadimpl.go index e6856e7221..1f081501c1 100644 --- a/internal/apiserver/reloadservice/reloadimpl.go +++ b/internal/apiserver/reloadservice/reloadimpl.go @@ -1,7 +1,7 @@ package reloadservice /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/reloadservice/reloadservice.go b/internal/apiserver/reloadservice/reloadservice.go index b58dccf8c1..d72ade0d1b 100644 --- a/internal/apiserver/reloadservice/reloadservice.go +++ b/internal/apiserver/reloadservice/reloadservice.go @@ -1,7 +1,7 @@ package reloadservice /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/restartservice/restartimpl.go b/internal/apiserver/restartservice/restartimpl.go index dc8b7e6853..f8ef1419c7 100644 --- a/internal/apiserver/restartservice/restartimpl.go +++ b/internal/apiserver/restartservice/restartimpl.go @@ -1,7 +1,7 @@ package restartservice /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/restartservice/restartservice.go b/internal/apiserver/restartservice/restartservice.go index 3dae8ca3ad..e8a29c97a1 100644 --- a/internal/apiserver/restartservice/restartservice.go +++ b/internal/apiserver/restartservice/restartservice.go @@ -1,7 +1,7 @@ package restartservice /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/root.go b/internal/apiserver/root.go index ea5c29b6a1..15dcd369b0 100644 --- a/internal/apiserver/root.go +++ b/internal/apiserver/root.go @@ -1,7 +1,7 @@ package apiserver /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/routing/doc.go b/internal/apiserver/routing/doc.go index c24eebfbe6..b050245796 100644 --- a/internal/apiserver/routing/doc.go +++ b/internal/apiserver/routing/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/routing/routes.go b/internal/apiserver/routing/routes.go index e90c8ea43b..fc804ff11a 100644 --- a/internal/apiserver/routing/routes.go +++ b/internal/apiserver/routing/routes.go @@ -1,7 +1,7 @@ package routing /* -Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/scheduleservice/scheduleimpl.go b/internal/apiserver/scheduleservice/scheduleimpl.go index 70384c87cf..1acc86cdf4 100644 --- a/internal/apiserver/scheduleservice/scheduleimpl.go +++ b/internal/apiserver/scheduleservice/scheduleimpl.go @@ -1,7 +1,7 @@ package scheduleservice /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/scheduleservice/scheduleservice.go b/internal/apiserver/scheduleservice/scheduleservice.go index 8f5ecdb26a..fae4961db6 100644 --- a/internal/apiserver/scheduleservice/scheduleservice.go +++ b/internal/apiserver/scheduleservice/scheduleservice.go @@ -1,7 +1,7 @@ package scheduleservice /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/statusservice/statusimpl.go b/internal/apiserver/statusservice/statusimpl.go index c7bf254c78..ff65e719ce 100644 --- a/internal/apiserver/statusservice/statusimpl.go +++ b/internal/apiserver/statusservice/statusimpl.go @@ -1,7 +1,7 @@ package statusservice /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/statusservice/statusservice.go b/internal/apiserver/statusservice/statusservice.go index 01de6c5687..2931c4d565 100644 --- a/internal/apiserver/statusservice/statusservice.go +++ b/internal/apiserver/statusservice/statusservice.go @@ -1,7 +1,7 @@ package statusservice /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/upgradeservice/upgradeimpl.go b/internal/apiserver/upgradeservice/upgradeimpl.go index 2c5e911bdf..b38f0ed271 100644 --- a/internal/apiserver/upgradeservice/upgradeimpl.go +++ b/internal/apiserver/upgradeservice/upgradeimpl.go @@ -1,7 +1,7 @@ package upgradeservice /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/upgradeservice/upgradeservice.go b/internal/apiserver/upgradeservice/upgradeservice.go index 0e8eb1d785..2c10d1b2ed 100644 --- a/internal/apiserver/upgradeservice/upgradeservice.go +++ b/internal/apiserver/upgradeservice/upgradeservice.go @@ -1,7 +1,7 @@ package upgradeservice /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/userservice/userimpl.go b/internal/apiserver/userservice/userimpl.go index ba57e86017..3991bc7217 100644 --- a/internal/apiserver/userservice/userimpl.go +++ b/internal/apiserver/userservice/userimpl.go @@ -1,7 +1,7 @@ package userservice /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/userservice/userimpl_test.go b/internal/apiserver/userservice/userimpl_test.go index 37e6fb6f0b..8dddcf50b8 100644 --- a/internal/apiserver/userservice/userimpl_test.go +++ b/internal/apiserver/userservice/userimpl_test.go @@ -1,7 +1,7 @@ package userservice /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/userservice/userservice.go b/internal/apiserver/userservice/userservice.go index 01a862483f..d64462a51a 100644 --- a/internal/apiserver/userservice/userservice.go +++ b/internal/apiserver/userservice/userservice.go @@ -1,7 +1,7 @@ package userservice /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/versionservice/versionimpl.go b/internal/apiserver/versionservice/versionimpl.go index fb62a4a9bb..ea21f7fdb4 100644 --- a/internal/apiserver/versionservice/versionimpl.go +++ b/internal/apiserver/versionservice/versionimpl.go @@ -1,7 +1,7 @@ package versionservice /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/versionservice/versionservice.go b/internal/apiserver/versionservice/versionservice.go index 80cf3833cb..1bf2dfe0b6 100644 --- a/internal/apiserver/versionservice/versionservice.go +++ b/internal/apiserver/versionservice/versionservice.go @@ -1,7 +1,7 @@ package versionservice /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/workflowservice/workflowimpl.go b/internal/apiserver/workflowservice/workflowimpl.go index c4edc6592d..91fd1919aa 100644 --- a/internal/apiserver/workflowservice/workflowimpl.go +++ b/internal/apiserver/workflowservice/workflowimpl.go @@ -1,7 +1,7 @@ package workflowservice /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/apiserver/workflowservice/workflowservice.go b/internal/apiserver/workflowservice/workflowservice.go index ef498e84a8..69f2f21af6 100644 --- a/internal/apiserver/workflowservice/workflowservice.go +++ b/internal/apiserver/workflowservice/workflowservice.go @@ -1,7 +1,7 @@ package workflowservice /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/config/annotations.go b/internal/config/annotations.go index 51bd1f1ec5..22b8737923 100644 --- a/internal/config/annotations.go +++ b/internal/config/annotations.go @@ -1,7 +1,7 @@ package config /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/config/defaults.go b/internal/config/defaults.go index a63340f16d..b4a454d5cf 100644 --- a/internal/config/defaults.go +++ b/internal/config/defaults.go @@ -1,7 +1,7 @@ package config /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/config/images.go b/internal/config/images.go index 019e0f400e..fe032605f2 100644 --- a/internal/config/images.go +++ b/internal/config/images.go @@ -1,7 +1,7 @@ package config /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/config/labels.go b/internal/config/labels.go index 4105406e83..eb60097f98 100644 --- a/internal/config/labels.go +++ b/internal/config/labels.go @@ -1,7 +1,7 @@ package config /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/config/pgoconfig.go b/internal/config/pgoconfig.go index ce9cb966d6..bf87e5253f 100644 --- a/internal/config/pgoconfig.go +++ b/internal/config/pgoconfig.go @@ -1,7 +1,7 @@ package config /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/config/secrets.go b/internal/config/secrets.go index b33e9d2f18..0d9a680b89 100644 --- a/internal/config/secrets.go +++ b/internal/config/secrets.go @@ -1,7 +1,7 @@ package config /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/config/volumes.go b/internal/config/volumes.go index c90d56bbb3..562c7cb105 100644 --- a/internal/config/volumes.go +++ b/internal/config/volumes.go @@ -1,7 +1,7 @@ package config /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/configmap/configmapcontroller.go b/internal/controller/configmap/configmapcontroller.go index d37d586b6e..b0c1b2b1bf 100644 --- a/internal/controller/configmap/configmapcontroller.go +++ b/internal/controller/configmap/configmapcontroller.go @@ -1,7 +1,7 @@ package configmap /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/configmap/synchandler.go b/internal/controller/configmap/synchandler.go index 249e7d21b3..9f7c3a42cf 100644 --- a/internal/controller/configmap/synchandler.go +++ b/internal/controller/configmap/synchandler.go @@ -1,7 +1,7 @@ package configmap /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/controllerutil.go b/internal/controller/controllerutil.go index 59591133de..fea8edfc71 100644 --- a/internal/controller/controllerutil.go +++ b/internal/controller/controllerutil.go @@ -1,7 +1,7 @@ package controller /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/job/backresthandler.go b/internal/controller/job/backresthandler.go index a64525bbdb..9d1e6f672a 100644 --- a/internal/controller/job/backresthandler.go +++ b/internal/controller/job/backresthandler.go @@ -1,7 +1,7 @@ package job /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/job/bootstraphandler.go b/internal/controller/job/bootstraphandler.go index a54f76d3be..84fbcdded1 100644 --- a/internal/controller/job/bootstraphandler.go +++ b/internal/controller/job/bootstraphandler.go @@ -1,7 +1,7 @@ package job /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/job/jobcontroller.go b/internal/controller/job/jobcontroller.go index b78330ef9d..dbcc708224 100644 --- a/internal/controller/job/jobcontroller.go +++ b/internal/controller/job/jobcontroller.go @@ -1,7 +1,7 @@ package job /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/job/jobevents.go b/internal/controller/job/jobevents.go index 1e930f2b07..3f49be3d4c 100644 --- a/internal/controller/job/jobevents.go +++ b/internal/controller/job/jobevents.go @@ -1,7 +1,7 @@ package job /* -Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/job/jobutil.go b/internal/controller/job/jobutil.go index 76135bcc32..9d742ed906 100644 --- a/internal/controller/job/jobutil.go +++ b/internal/controller/job/jobutil.go @@ -1,7 +1,7 @@ package job /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/job/pgdumphandler.go b/internal/controller/job/pgdumphandler.go index 934e49e8a0..ea7a0a8a50 100644 --- a/internal/controller/job/pgdumphandler.go +++ b/internal/controller/job/pgdumphandler.go @@ -1,7 +1,7 @@ package job /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/job/rmdatahandler.go b/internal/controller/job/rmdatahandler.go index 0fce9efe07..86e226a850 100644 --- a/internal/controller/job/rmdatahandler.go +++ b/internal/controller/job/rmdatahandler.go @@ -1,7 +1,7 @@ package job /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/manager/controllermanager.go b/internal/controller/manager/controllermanager.go index 7670ac10ee..18721ead64 100644 --- a/internal/controller/manager/controllermanager.go +++ b/internal/controller/manager/controllermanager.go @@ -1,7 +1,7 @@ package manager /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/manager/rbac.go b/internal/controller/manager/rbac.go index ea0d845f9c..e33d0a4dd9 100644 --- a/internal/controller/manager/rbac.go +++ b/internal/controller/manager/rbac.go @@ -1,7 +1,7 @@ package manager /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/namespace/namespacecontroller.go b/internal/controller/namespace/namespacecontroller.go index 4fb53032f3..757307fd53 100644 --- a/internal/controller/namespace/namespacecontroller.go +++ b/internal/controller/namespace/namespacecontroller.go @@ -1,7 +1,7 @@ package namespace /* -Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/pgcluster/pgclustercontroller.go b/internal/controller/pgcluster/pgclustercontroller.go index 25e03b33bc..ce095bbdd4 100644 --- a/internal/controller/pgcluster/pgclustercontroller.go +++ b/internal/controller/pgcluster/pgclustercontroller.go @@ -1,7 +1,7 @@ package pgcluster /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/pgpolicy/pgpolicycontroller.go b/internal/controller/pgpolicy/pgpolicycontroller.go index 52ce680af1..53ffd4fa78 100644 --- a/internal/controller/pgpolicy/pgpolicycontroller.go +++ b/internal/controller/pgpolicy/pgpolicycontroller.go @@ -1,7 +1,7 @@ package pgpolicy /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/pgreplica/pgreplicacontroller.go b/internal/controller/pgreplica/pgreplicacontroller.go index d6af6485b2..2468ad0bc5 100644 --- a/internal/controller/pgreplica/pgreplicacontroller.go +++ b/internal/controller/pgreplica/pgreplicacontroller.go @@ -1,7 +1,7 @@ package pgreplica /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/pgtask/backresthandler.go b/internal/controller/pgtask/backresthandler.go index d650164b02..eba65e67b6 100644 --- a/internal/controller/pgtask/backresthandler.go +++ b/internal/controller/pgtask/backresthandler.go @@ -1,7 +1,7 @@ package pgtask /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/pgtask/pgtaskcontroller.go b/internal/controller/pgtask/pgtaskcontroller.go index bf2ed904b3..88b59fb37d 100644 --- a/internal/controller/pgtask/pgtaskcontroller.go +++ b/internal/controller/pgtask/pgtaskcontroller.go @@ -1,7 +1,7 @@ package pgtask /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/pod/inithandler.go b/internal/controller/pod/inithandler.go index 49304df7fe..d57fc72a64 100644 --- a/internal/controller/pod/inithandler.go +++ b/internal/controller/pod/inithandler.go @@ -1,7 +1,7 @@ package pod /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/pod/podcontroller.go b/internal/controller/pod/podcontroller.go index bf339e7bb5..58b90b9917 100644 --- a/internal/controller/pod/podcontroller.go +++ b/internal/controller/pod/podcontroller.go @@ -1,7 +1,7 @@ package pod /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/pod/podevents.go b/internal/controller/pod/podevents.go index b87fa41fe9..00e901458f 100644 --- a/internal/controller/pod/podevents.go +++ b/internal/controller/pod/podevents.go @@ -1,7 +1,7 @@ package pod /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/controller/pod/promotionhandler.go b/internal/controller/pod/promotionhandler.go index a8ad3ba261..b7de3fc2df 100644 --- a/internal/controller/pod/promotionhandler.go +++ b/internal/controller/pod/promotionhandler.go @@ -1,7 +1,7 @@ package pod /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/client_config.go b/internal/kubeapi/client_config.go index 7bea0453ee..883b8d12fa 100644 --- a/internal/kubeapi/client_config.go +++ b/internal/kubeapi/client_config.go @@ -1,7 +1,7 @@ package kubeapi /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/endpoints.go b/internal/kubeapi/endpoints.go index df9d157aa6..bb8517439c 100644 --- a/internal/kubeapi/endpoints.go +++ b/internal/kubeapi/endpoints.go @@ -1,7 +1,7 @@ package kubeapi /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/errors.go b/internal/kubeapi/errors.go index b3f79e73ca..783ed065ee 100644 --- a/internal/kubeapi/errors.go +++ b/internal/kubeapi/errors.go @@ -1,7 +1,7 @@ package kubeapi /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/exec.go b/internal/kubeapi/exec.go index d807980c31..29811e0f89 100644 --- a/internal/kubeapi/exec.go +++ b/internal/kubeapi/exec.go @@ -1,7 +1,7 @@ package kubeapi /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/fake/clientset.go b/internal/kubeapi/fake/clientset.go index 7623fea9c3..3265e42280 100644 --- a/internal/kubeapi/fake/clientset.go +++ b/internal/kubeapi/fake/clientset.go @@ -1,7 +1,7 @@ package fake /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/fake/fakeclients.go b/internal/kubeapi/fake/fakeclients.go index f45865b942..04e950e622 100644 --- a/internal/kubeapi/fake/fakeclients.go +++ b/internal/kubeapi/fake/fakeclients.go @@ -1,7 +1,7 @@ package fake /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/patch.go b/internal/kubeapi/patch.go index 2042127db1..5ce73077ab 100644 --- a/internal/kubeapi/patch.go +++ b/internal/kubeapi/patch.go @@ -1,7 +1,7 @@ package kubeapi /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/patch_test.go b/internal/kubeapi/patch_test.go index a751f5f04b..ddb41a4552 100644 --- a/internal/kubeapi/patch_test.go +++ b/internal/kubeapi/patch_test.go @@ -1,7 +1,7 @@ package kubeapi /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/volumes.go b/internal/kubeapi/volumes.go index 26e1b2dd63..6014ee39f4 100644 --- a/internal/kubeapi/volumes.go +++ b/internal/kubeapi/volumes.go @@ -1,7 +1,7 @@ package kubeapi /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/kubeapi/volumes_test.go b/internal/kubeapi/volumes_test.go index 921f454dad..6e43b3e5ca 100644 --- a/internal/kubeapi/volumes_test.go +++ b/internal/kubeapi/volumes_test.go @@ -1,7 +1,7 @@ package kubeapi /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/logging/loglib.go b/internal/logging/loglib.go index e36e5ba199..846544c9f5 100644 --- a/internal/logging/loglib.go +++ b/internal/logging/loglib.go @@ -2,7 +2,7 @@ package logging /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/ns/nslogic.go b/internal/ns/nslogic.go index 58acc7c849..6148f33de7 100644 --- a/internal/ns/nslogic.go +++ b/internal/ns/nslogic.go @@ -1,7 +1,7 @@ package ns /* -Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/backrest/backup.go b/internal/operator/backrest/backup.go index b37e19836a..c624ed6f25 100644 --- a/internal/operator/backrest/backup.go +++ b/internal/operator/backrest/backup.go @@ -1,7 +1,7 @@ package backrest /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/backrest/repo.go b/internal/operator/backrest/repo.go index 7c883540cf..bed14f834b 100644 --- a/internal/operator/backrest/repo.go +++ b/internal/operator/backrest/repo.go @@ -1,7 +1,7 @@ package backrest /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/backrest/restore.go b/internal/operator/backrest/restore.go index 2523d4c60c..eafcdcbf44 100644 --- a/internal/operator/backrest/restore.go +++ b/internal/operator/backrest/restore.go @@ -1,7 +1,7 @@ package backrest /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/backrest/stanza.go b/internal/operator/backrest/stanza.go index 6d7166da33..ae4a2e29b8 100644 --- a/internal/operator/backrest/stanza.go +++ b/internal/operator/backrest/stanza.go @@ -1,7 +1,7 @@ package backrest /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/cluster.go b/internal/operator/cluster/cluster.go index 9acb5025f2..8fe574021c 100644 --- a/internal/operator/cluster/cluster.go +++ b/internal/operator/cluster/cluster.go @@ -4,7 +4,7 @@ package cluster /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/clusterlogic.go b/internal/operator/cluster/clusterlogic.go index 8a5ac33e3c..70f40c4711 100644 --- a/internal/operator/cluster/clusterlogic.go +++ b/internal/operator/cluster/clusterlogic.go @@ -4,7 +4,7 @@ package cluster /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/common.go b/internal/operator/cluster/common.go index d3ee882510..a17ccec7ef 100644 --- a/internal/operator/cluster/common.go +++ b/internal/operator/cluster/common.go @@ -1,7 +1,7 @@ package cluster /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/common_test.go b/internal/operator/cluster/common_test.go index e662a7a3d6..aaad3a3292 100644 --- a/internal/operator/cluster/common_test.go +++ b/internal/operator/cluster/common_test.go @@ -1,7 +1,7 @@ package cluster /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/exporter.go b/internal/operator/cluster/exporter.go index b14b81df5f..9969d6f75a 100644 --- a/internal/operator/cluster/exporter.go +++ b/internal/operator/cluster/exporter.go @@ -1,7 +1,7 @@ package cluster /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/pgadmin.go b/internal/operator/cluster/pgadmin.go index fd3d9a4fe2..53f456100c 100644 --- a/internal/operator/cluster/pgadmin.go +++ b/internal/operator/cluster/pgadmin.go @@ -1,7 +1,7 @@ package cluster /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/pgbadger.go b/internal/operator/cluster/pgbadger.go index 3e67a93bf3..d7af20bf0d 100644 --- a/internal/operator/cluster/pgbadger.go +++ b/internal/operator/cluster/pgbadger.go @@ -1,7 +1,7 @@ package cluster /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/pgbouncer.go b/internal/operator/cluster/pgbouncer.go index 6c7004d908..a61bef2598 100644 --- a/internal/operator/cluster/pgbouncer.go +++ b/internal/operator/cluster/pgbouncer.go @@ -1,7 +1,7 @@ package cluster /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/pgbouncer_test.go b/internal/operator/cluster/pgbouncer_test.go index a789f3bb2d..4b23048288 100644 --- a/internal/operator/cluster/pgbouncer_test.go +++ b/internal/operator/cluster/pgbouncer_test.go @@ -1,7 +1,7 @@ package cluster /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/rolling.go b/internal/operator/cluster/rolling.go index 5dc55c9e92..867c23be20 100644 --- a/internal/operator/cluster/rolling.go +++ b/internal/operator/cluster/rolling.go @@ -1,7 +1,7 @@ package cluster /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/service.go b/internal/operator/cluster/service.go index d0aa6f7487..0a740b79c4 100644 --- a/internal/operator/cluster/service.go +++ b/internal/operator/cluster/service.go @@ -4,7 +4,7 @@ package cluster /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/standby.go b/internal/operator/cluster/standby.go index 9c645c24da..ab6c5ef2ce 100644 --- a/internal/operator/cluster/standby.go +++ b/internal/operator/cluster/standby.go @@ -1,7 +1,7 @@ package cluster /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/cluster/upgrade.go b/internal/operator/cluster/upgrade.go index 3fb2d39e8c..620a75ada3 100644 --- a/internal/operator/cluster/upgrade.go +++ b/internal/operator/cluster/upgrade.go @@ -1,7 +1,7 @@ package cluster /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/clusterutilities.go b/internal/operator/clusterutilities.go index 0a2fdaed69..df56891e6b 100644 --- a/internal/operator/clusterutilities.go +++ b/internal/operator/clusterutilities.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/clusterutilities_test.go b/internal/operator/clusterutilities_test.go index d858d3245a..ea0a37dbb9 100644 --- a/internal/operator/clusterutilities_test.go +++ b/internal/operator/clusterutilities_test.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/common.go b/internal/operator/common.go index ba1eb47e21..ead5047318 100644 --- a/internal/operator/common.go +++ b/internal/operator/common.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/common_test.go b/internal/operator/common_test.go index 6217b66fab..40e1c39374 100644 --- a/internal/operator/common_test.go +++ b/internal/operator/common_test.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/config/configutil.go b/internal/operator/config/configutil.go index bd20522cf7..3efc10d57b 100644 --- a/internal/operator/config/configutil.go +++ b/internal/operator/config/configutil.go @@ -1,7 +1,7 @@ package config /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/config/dcs.go b/internal/operator/config/dcs.go index 53fdcbd3e3..43ad7d7ac5 100644 --- a/internal/operator/config/dcs.go +++ b/internal/operator/config/dcs.go @@ -1,7 +1,7 @@ package config /* - Copyright 2020 - 2022 Crunchy Data Solutions, Ind. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/config/localdb.go b/internal/operator/config/localdb.go index dc4c759f6b..b8326c3ade 100644 --- a/internal/operator/config/localdb.go +++ b/internal/operator/config/localdb.go @@ -1,7 +1,7 @@ package config /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inl. + Copyright 2020 - 2023 Crunchy Data Solutions, Inl. 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 diff --git a/internal/operator/failover.go b/internal/operator/failover.go index ba3f841a6d..e75e914466 100644 --- a/internal/operator/failover.go +++ b/internal/operator/failover.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/failover_test.go b/internal/operator/failover_test.go index 9b41035fef..c2a7a78a7e 100644 --- a/internal/operator/failover_test.go +++ b/internal/operator/failover_test.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2021 - 2022 Crunchy Data Solutions, Inc. + Copyright 2021 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/operatorupgrade/version-check.go b/internal/operator/operatorupgrade/version-check.go index ea5327b951..612dd264e8 100644 --- a/internal/operator/operatorupgrade/version-check.go +++ b/internal/operator/operatorupgrade/version-check.go @@ -1,7 +1,7 @@ package operatorupgrade /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/pgbackrest.go b/internal/operator/pgbackrest.go index 81b440c921..d46b91eed0 100644 --- a/internal/operator/pgbackrest.go +++ b/internal/operator/pgbackrest.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/pgbackrest_test.go b/internal/operator/pgbackrest_test.go index f853e0e363..ed4e11085b 100644 --- a/internal/operator/pgbackrest_test.go +++ b/internal/operator/pgbackrest_test.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/pgdump/dump.go b/internal/operator/pgdump/dump.go index fb8b437122..a124c5314d 100644 --- a/internal/operator/pgdump/dump.go +++ b/internal/operator/pgdump/dump.go @@ -1,7 +1,7 @@ package pgdump /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/pgdump/restore.go b/internal/operator/pgdump/restore.go index a09ef6a098..43cce4d4c7 100644 --- a/internal/operator/pgdump/restore.go +++ b/internal/operator/pgdump/restore.go @@ -1,7 +1,7 @@ package pgdump /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/pvc/pvc.go b/internal/operator/pvc/pvc.go index 069ba34a25..dd530933ec 100644 --- a/internal/operator/pvc/pvc.go +++ b/internal/operator/pvc/pvc.go @@ -1,7 +1,7 @@ package pvc /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/storage.go b/internal/operator/storage.go index ba7f512a9a..dd168457de 100644 --- a/internal/operator/storage.go +++ b/internal/operator/storage.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/storage_test.go b/internal/operator/storage_test.go index 5c817c1780..d6c43466fb 100644 --- a/internal/operator/storage_test.go +++ b/internal/operator/storage_test.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/switchover.go b/internal/operator/switchover.go index 56a21fb7dc..7b45f93249 100644 --- a/internal/operator/switchover.go +++ b/internal/operator/switchover.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2021 - 2022 Crunchy Data Solutions, Inc. + Copyright 2021 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/switchover_test.go b/internal/operator/switchover_test.go index e11cf08061..50258d32d1 100644 --- a/internal/operator/switchover_test.go +++ b/internal/operator/switchover_test.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2021 - 2022 Crunchy Data Solutions, Inc. + Copyright 2021 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/task/applypolicies.go b/internal/operator/task/applypolicies.go index 5eabbcad3c..f21a4ca9de 100644 --- a/internal/operator/task/applypolicies.go +++ b/internal/operator/task/applypolicies.go @@ -1,7 +1,7 @@ package task /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/task/rmdata.go b/internal/operator/task/rmdata.go index fbf0087dd3..643406848b 100644 --- a/internal/operator/task/rmdata.go +++ b/internal/operator/task/rmdata.go @@ -1,7 +1,7 @@ package task /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/task/workflow.go b/internal/operator/task/workflow.go index d6920f3c95..02f58369b4 100644 --- a/internal/operator/task/workflow.go +++ b/internal/operator/task/workflow.go @@ -1,7 +1,7 @@ package task /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/operator/wal.go b/internal/operator/wal.go index ca0889ca50..9005a27141 100644 --- a/internal/operator/wal.go +++ b/internal/operator/wal.go @@ -1,7 +1,7 @@ package operator /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/patroni/doc.go b/internal/patroni/doc.go index 62c153097e..bd7055962a 100644 --- a/internal/patroni/doc.go +++ b/internal/patroni/doc.go @@ -4,7 +4,7 @@ package patroni /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/patroni/patroni.go b/internal/patroni/patroni.go index e22aaf49f4..3570c25b9f 100644 --- a/internal/patroni/patroni.go +++ b/internal/patroni/patroni.go @@ -1,7 +1,7 @@ package patroni /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/pgadmin/backoff.go b/internal/pgadmin/backoff.go index 94c2b1b2db..ab9176ae4c 100644 --- a/internal/pgadmin/backoff.go +++ b/internal/pgadmin/backoff.go @@ -1,7 +1,7 @@ package pgadmin /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/pgadmin/backoff_test.go b/internal/pgadmin/backoff_test.go index 5a04a69c6e..b87d8db6d7 100644 --- a/internal/pgadmin/backoff_test.go +++ b/internal/pgadmin/backoff_test.go @@ -1,7 +1,7 @@ package pgadmin /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/pgadmin/crypto.go b/internal/pgadmin/crypto.go index 6b116a6b0d..f602e63dca 100644 --- a/internal/pgadmin/crypto.go +++ b/internal/pgadmin/crypto.go @@ -1,7 +1,7 @@ package pgadmin /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/pgadmin/crypto_test.go b/internal/pgadmin/crypto_test.go index bf301f2a2e..6f11a665a5 100644 --- a/internal/pgadmin/crypto_test.go +++ b/internal/pgadmin/crypto_test.go @@ -1,7 +1,7 @@ package pgadmin /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/pgadmin/doc.go b/internal/pgadmin/doc.go index d02d6feb3c..f822a2051c 100644 --- a/internal/pgadmin/doc.go +++ b/internal/pgadmin/doc.go @@ -4,7 +4,7 @@ database which powers pgadmin */ package pgadmin /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/pgadmin/hash.go b/internal/pgadmin/hash.go index cc2f2d2e5f..4f7ffdd5e7 100644 --- a/internal/pgadmin/hash.go +++ b/internal/pgadmin/hash.go @@ -1,7 +1,7 @@ package pgadmin /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/pgadmin/logic.go b/internal/pgadmin/logic.go index da8a56bcc4..3632a6fbb8 100644 --- a/internal/pgadmin/logic.go +++ b/internal/pgadmin/logic.go @@ -1,7 +1,7 @@ package pgadmin /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/pgadmin/runner.go b/internal/pgadmin/runner.go index f4f948728e..85f4586c76 100644 --- a/internal/pgadmin/runner.go +++ b/internal/pgadmin/runner.go @@ -1,7 +1,7 @@ package pgadmin /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/pgadmin/server.go b/internal/pgadmin/server.go index dcc68dd26c..8ee8cd4650 100644 --- a/internal/pgadmin/server.go +++ b/internal/pgadmin/server.go @@ -1,7 +1,7 @@ package pgadmin /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/postgres/doc.go b/internal/postgres/doc.go index 8010931373..5066f642d4 100644 --- a/internal/postgres/doc.go +++ b/internal/postgres/doc.go @@ -5,7 +5,7 @@ package postgres /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/postgres/password/doc.go b/internal/postgres/password/doc.go index 8950878ee2..4ac3b5fd1d 100644 --- a/internal/postgres/password/doc.go +++ b/internal/postgres/password/doc.go @@ -4,7 +4,7 @@ package password /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/postgres/password/md5.go b/internal/postgres/password/md5.go index 537b07fdfd..4973140fae 100644 --- a/internal/postgres/password/md5.go +++ b/internal/postgres/password/md5.go @@ -1,7 +1,7 @@ package password /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/postgres/password/md5_test.go b/internal/postgres/password/md5_test.go index 9646ae283b..5f55a8a222 100644 --- a/internal/postgres/password/md5_test.go +++ b/internal/postgres/password/md5_test.go @@ -1,7 +1,7 @@ package password /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/postgres/password/password.go b/internal/postgres/password/password.go index 072570f1cd..a667921707 100644 --- a/internal/postgres/password/password.go +++ b/internal/postgres/password/password.go @@ -1,7 +1,7 @@ package password /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/postgres/password/password_test.go b/internal/postgres/password/password_test.go index 7bd2afee9e..dda95e1dc4 100644 --- a/internal/postgres/password/password_test.go +++ b/internal/postgres/password/password_test.go @@ -1,7 +1,7 @@ package password /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/postgres/password/scram.go b/internal/postgres/password/scram.go index 62f4bf2649..6e0f75e22b 100644 --- a/internal/postgres/password/scram.go +++ b/internal/postgres/password/scram.go @@ -1,7 +1,7 @@ package password /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/postgres/password/scram_test.go b/internal/postgres/password/scram_test.go index d20cfa0f40..b568688e37 100644 --- a/internal/postgres/password/scram_test.go +++ b/internal/postgres/password/scram_test.go @@ -1,7 +1,7 @@ package password /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/tlsutil/primitives.go b/internal/tlsutil/primitives.go index 7c56f9ece1..5059d93142 100644 --- a/internal/tlsutil/primitives.go +++ b/internal/tlsutil/primitives.go @@ -1,7 +1,7 @@ package tlsutil /* -Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/tlsutil/primitives_test.go b/internal/tlsutil/primitives_test.go index 31df75d46b..fc18100446 100644 --- a/internal/tlsutil/primitives_test.go +++ b/internal/tlsutil/primitives_test.go @@ -1,7 +1,7 @@ package tlsutil /* -Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/backrest.go b/internal/util/backrest.go index d8121b38a6..123592c7f3 100644 --- a/internal/util/backrest.go +++ b/internal/util/backrest.go @@ -1,7 +1,7 @@ package util /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/cluster.go b/internal/util/cluster.go index 0b35dbc9b6..3cc68e7e41 100644 --- a/internal/util/cluster.go +++ b/internal/util/cluster.go @@ -1,7 +1,7 @@ package util /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/cluster_test.go b/internal/util/cluster_test.go index 3df855e78c..b12f441b7f 100644 --- a/internal/util/cluster_test.go +++ b/internal/util/cluster_test.go @@ -1,7 +1,7 @@ package util /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/exporter.go b/internal/util/exporter.go index b9b39d47a2..f8d1a42447 100644 --- a/internal/util/exporter.go +++ b/internal/util/exporter.go @@ -1,7 +1,7 @@ package util /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/exporter_test.go b/internal/util/exporter_test.go index 9ea7fe0be2..9a80754093 100644 --- a/internal/util/exporter_test.go +++ b/internal/util/exporter_test.go @@ -1,7 +1,7 @@ package util /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/failover.go b/internal/util/failover.go index fe743753df..7e0a4165ea 100644 --- a/internal/util/failover.go +++ b/internal/util/failover.go @@ -1,7 +1,7 @@ package util /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/pgbouncer.go b/internal/util/pgbouncer.go index 7771dffa2a..3039ac1abb 100644 --- a/internal/util/pgbouncer.go +++ b/internal/util/pgbouncer.go @@ -1,7 +1,7 @@ package util /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/policy.go b/internal/util/policy.go index 467cf124f3..0be895137e 100644 --- a/internal/util/policy.go +++ b/internal/util/policy.go @@ -1,7 +1,7 @@ package util /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/secrets.go b/internal/util/secrets.go index 046f3df73a..d908eba1aa 100644 --- a/internal/util/secrets.go +++ b/internal/util/secrets.go @@ -1,7 +1,7 @@ package util /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/secrets_test.go b/internal/util/secrets_test.go index 2754541d20..4b8676e7ca 100644 --- a/internal/util/secrets_test.go +++ b/internal/util/secrets_test.go @@ -1,7 +1,7 @@ package util /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/ssh.go b/internal/util/ssh.go index e835bbb470..c916abcd28 100644 --- a/internal/util/ssh.go +++ b/internal/util/ssh.go @@ -1,7 +1,7 @@ package util /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/internal/util/util.go b/internal/util/util.go index b3bbaa74fb..ceb20cef80 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -1,7 +1,7 @@ package util /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/cluster.go b/pkg/apis/crunchydata.com/v1/cluster.go index d1244f779d..6626f28b13 100644 --- a/pkg/apis/crunchydata.com/v1/cluster.go +++ b/pkg/apis/crunchydata.com/v1/cluster.go @@ -1,7 +1,7 @@ package v1 /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/cluster_test.go b/pkg/apis/crunchydata.com/v1/cluster_test.go index f26938594d..9bc23ef8d9 100644 --- a/pkg/apis/crunchydata.com/v1/cluster_test.go +++ b/pkg/apis/crunchydata.com/v1/cluster_test.go @@ -1,7 +1,7 @@ package v1 /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/common.go b/pkg/apis/crunchydata.com/v1/common.go index 46ed360ecd..07bfd11934 100644 --- a/pkg/apis/crunchydata.com/v1/common.go +++ b/pkg/apis/crunchydata.com/v1/common.go @@ -1,7 +1,7 @@ package v1 /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/common_test.go b/pkg/apis/crunchydata.com/v1/common_test.go index 853be90ba1..353d92f3e5 100644 --- a/pkg/apis/crunchydata.com/v1/common_test.go +++ b/pkg/apis/crunchydata.com/v1/common_test.go @@ -1,7 +1,7 @@ package v1 /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/doc.go b/pkg/apis/crunchydata.com/v1/doc.go index e20a4a55f5..554ad16831 100644 --- a/pkg/apis/crunchydata.com/v1/doc.go +++ b/pkg/apis/crunchydata.com/v1/doc.go @@ -111,7 +111,7 @@ package v1 // +k8s:deepcopy-gen=package,register /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/errors.go b/pkg/apis/crunchydata.com/v1/errors.go index f1ee079ce8..ec2c8dfc16 100644 --- a/pkg/apis/crunchydata.com/v1/errors.go +++ b/pkg/apis/crunchydata.com/v1/errors.go @@ -3,7 +3,7 @@ package v1 import "errors" /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/policy.go b/pkg/apis/crunchydata.com/v1/policy.go index 07b669f49e..57370ec932 100644 --- a/pkg/apis/crunchydata.com/v1/policy.go +++ b/pkg/apis/crunchydata.com/v1/policy.go @@ -1,7 +1,7 @@ package v1 /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/register.go b/pkg/apis/crunchydata.com/v1/register.go index 53bc23aeac..6b5926e412 100644 --- a/pkg/apis/crunchydata.com/v1/register.go +++ b/pkg/apis/crunchydata.com/v1/register.go @@ -1,7 +1,7 @@ package v1 /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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/pkg/apis/crunchydata.com/v1/replica.go b/pkg/apis/crunchydata.com/v1/replica.go index f4100305c4..990bb2d050 100644 --- a/pkg/apis/crunchydata.com/v1/replica.go +++ b/pkg/apis/crunchydata.com/v1/replica.go @@ -1,7 +1,7 @@ package v1 /* - Copyright 2018 - 2022 Crunchy Data Solutions, Inc. + Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/task.go b/pkg/apis/crunchydata.com/v1/task.go index 6d4a5a9657..1f5bbfa29b 100644 --- a/pkg/apis/crunchydata.com/v1/task.go +++ b/pkg/apis/crunchydata.com/v1/task.go @@ -1,7 +1,7 @@ package v1 /* - Copyright 2017 - 2022 Crunchy Data Solutions, Inc. + Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go b/pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go index 708a49f64f..04a5793426 100644 --- a/pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go +++ b/pkg/apis/crunchydata.com/v1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/backrestmsgs.go b/pkg/apiservermsgs/backrestmsgs.go index 887fed79a9..a87b72b987 100644 --- a/pkg/apiservermsgs/backrestmsgs.go +++ b/pkg/apiservermsgs/backrestmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/catmsgs.go b/pkg/apiservermsgs/catmsgs.go index b1eb5dd69f..23500245ab 100644 --- a/pkg/apiservermsgs/catmsgs.go +++ b/pkg/apiservermsgs/catmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/clustermsgs.go b/pkg/apiservermsgs/clustermsgs.go index e3d4ae83e9..2b81779223 100644 --- a/pkg/apiservermsgs/clustermsgs.go +++ b/pkg/apiservermsgs/clustermsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/common.go b/pkg/apiservermsgs/common.go index ac3b79aed1..8883c828d2 100644 --- a/pkg/apiservermsgs/common.go +++ b/pkg/apiservermsgs/common.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/configmsgs.go b/pkg/apiservermsgs/configmsgs.go index a7f0631401..99d300841d 100644 --- a/pkg/apiservermsgs/configmsgs.go +++ b/pkg/apiservermsgs/configmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/dfmsgs.go b/pkg/apiservermsgs/dfmsgs.go index 7e7e980eac..d7117ce3b4 100644 --- a/pkg/apiservermsgs/dfmsgs.go +++ b/pkg/apiservermsgs/dfmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/failovermsgs.go b/pkg/apiservermsgs/failovermsgs.go index 30040c016e..425d9b447f 100644 --- a/pkg/apiservermsgs/failovermsgs.go +++ b/pkg/apiservermsgs/failovermsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/labelmsgs.go b/pkg/apiservermsgs/labelmsgs.go index 44019b3ace..7fc0289170 100644 --- a/pkg/apiservermsgs/labelmsgs.go +++ b/pkg/apiservermsgs/labelmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/namespacemsgs.go b/pkg/apiservermsgs/namespacemsgs.go index dda8a3fe3a..b72ccfa7dc 100644 --- a/pkg/apiservermsgs/namespacemsgs.go +++ b/pkg/apiservermsgs/namespacemsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/pgadminmsgs.go b/pkg/apiservermsgs/pgadminmsgs.go index 9241a9cc43..4c63347545 100644 --- a/pkg/apiservermsgs/pgadminmsgs.go +++ b/pkg/apiservermsgs/pgadminmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/pgbouncermsgs.go b/pkg/apiservermsgs/pgbouncermsgs.go index 73083f0c84..fd34a22aaa 100644 --- a/pkg/apiservermsgs/pgbouncermsgs.go +++ b/pkg/apiservermsgs/pgbouncermsgs.go @@ -3,7 +3,7 @@ package apiservermsgs import v1 "k8s.io/api/core/v1" /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/pgdumpmsgs.go b/pkg/apiservermsgs/pgdumpmsgs.go index 58753f66cc..5133978241 100644 --- a/pkg/apiservermsgs/pgdumpmsgs.go +++ b/pkg/apiservermsgs/pgdumpmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/pgorolemsgs.go b/pkg/apiservermsgs/pgorolemsgs.go index 1705452624..43b488f3d8 100644 --- a/pkg/apiservermsgs/pgorolemsgs.go +++ b/pkg/apiservermsgs/pgorolemsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/pgousermsgs.go b/pkg/apiservermsgs/pgousermsgs.go index e636c71b61..e4189a2a7e 100644 --- a/pkg/apiservermsgs/pgousermsgs.go +++ b/pkg/apiservermsgs/pgousermsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/policymsgs.go b/pkg/apiservermsgs/policymsgs.go index b733c5373e..793815d8f9 100644 --- a/pkg/apiservermsgs/policymsgs.go +++ b/pkg/apiservermsgs/policymsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/pvcmsgs.go b/pkg/apiservermsgs/pvcmsgs.go index 7c4f1a366c..421380027a 100644 --- a/pkg/apiservermsgs/pvcmsgs.go +++ b/pkg/apiservermsgs/pvcmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/reloadmsgs.go b/pkg/apiservermsgs/reloadmsgs.go index ae23783d05..cbcdfcc624 100644 --- a/pkg/apiservermsgs/reloadmsgs.go +++ b/pkg/apiservermsgs/reloadmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/restartmsgs.go b/pkg/apiservermsgs/restartmsgs.go index 495c4ae7b1..e9d23de05e 100644 --- a/pkg/apiservermsgs/restartmsgs.go +++ b/pkg/apiservermsgs/restartmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/schedulemsgs.go b/pkg/apiservermsgs/schedulemsgs.go index 529d60ddb6..e64d10bf32 100644 --- a/pkg/apiservermsgs/schedulemsgs.go +++ b/pkg/apiservermsgs/schedulemsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/statusmsgs.go b/pkg/apiservermsgs/statusmsgs.go index 923cd7fe77..2a4013d76b 100644 --- a/pkg/apiservermsgs/statusmsgs.go +++ b/pkg/apiservermsgs/statusmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/upgrademsgs.go b/pkg/apiservermsgs/upgrademsgs.go index 7e30d254de..dcaa5ead61 100644 --- a/pkg/apiservermsgs/upgrademsgs.go +++ b/pkg/apiservermsgs/upgrademsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/usermsgs.go b/pkg/apiservermsgs/usermsgs.go index eabbe3e3b4..9f830c7a3b 100644 --- a/pkg/apiservermsgs/usermsgs.go +++ b/pkg/apiservermsgs/usermsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/versionmsgs.go b/pkg/apiservermsgs/versionmsgs.go index 91618ec76a..67c437f175 100644 --- a/pkg/apiservermsgs/versionmsgs.go +++ b/pkg/apiservermsgs/versionmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +Copyright 2017 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/watchmsgs.go b/pkg/apiservermsgs/watchmsgs.go index 4d6c48e65c..be5fe353cf 100644 --- a/pkg/apiservermsgs/watchmsgs.go +++ b/pkg/apiservermsgs/watchmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2019 - 2022 Crunchy Data Solutions, Inc. +Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/apiservermsgs/workflowmsgs.go b/pkg/apiservermsgs/workflowmsgs.go index 4c4be0bd45..54c2dd14c2 100644 --- a/pkg/apiservermsgs/workflowmsgs.go +++ b/pkg/apiservermsgs/workflowmsgs.go @@ -1,7 +1,7 @@ package apiservermsgs /* -Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +Copyright 2018 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/events/eventing.go b/pkg/events/eventing.go index e69355b1b2..9b7b32a33a 100644 --- a/pkg/events/eventing.go +++ b/pkg/events/eventing.go @@ -1,7 +1,7 @@ package events /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/events/eventtype.go b/pkg/events/eventtype.go index e538b8d030..6ee205094c 100644 --- a/pkg/events/eventtype.go +++ b/pkg/events/eventtype.go @@ -1,7 +1,7 @@ package events /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/events/pgoeventtype.go b/pkg/events/pgoeventtype.go index 305c9197f0..566294e302 100644 --- a/pkg/events/pgoeventtype.go +++ b/pkg/events/pgoeventtype.go @@ -1,7 +1,7 @@ package events /* - Copyright 2019 - 2022 Crunchy Data Solutions, Inc. + Copyright 2019 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/clientset.go b/pkg/generated/clientset/versioned/clientset.go index 73eb41a8a4..4faabe8ad5 100644 --- a/pkg/generated/clientset/versioned/clientset.go +++ b/pkg/generated/clientset/versioned/clientset.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/doc.go b/pkg/generated/clientset/versioned/doc.go index c8faf9c342..663a401c6c 100644 --- a/pkg/generated/clientset/versioned/doc.go +++ b/pkg/generated/clientset/versioned/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/fake/clientset_generated.go b/pkg/generated/clientset/versioned/fake/clientset_generated.go index 168269e377..0a25103a8c 100644 --- a/pkg/generated/clientset/versioned/fake/clientset_generated.go +++ b/pkg/generated/clientset/versioned/fake/clientset_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/fake/doc.go b/pkg/generated/clientset/versioned/fake/doc.go index 79128b5478..130eb64878 100644 --- a/pkg/generated/clientset/versioned/fake/doc.go +++ b/pkg/generated/clientset/versioned/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/fake/register.go b/pkg/generated/clientset/versioned/fake/register.go index 93abfdf223..26b3aca655 100644 --- a/pkg/generated/clientset/versioned/fake/register.go +++ b/pkg/generated/clientset/versioned/fake/register.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/scheme/doc.go b/pkg/generated/clientset/versioned/scheme/doc.go index 264e52dac0..061044abc0 100644 --- a/pkg/generated/clientset/versioned/scheme/doc.go +++ b/pkg/generated/clientset/versioned/scheme/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/scheme/register.go b/pkg/generated/clientset/versioned/scheme/register.go index de17632bfe..0ed3f8025d 100644 --- a/pkg/generated/clientset/versioned/scheme/register.go +++ b/pkg/generated/clientset/versioned/scheme/register.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/crunchydata.com_client.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/crunchydata.com_client.go index dfd0dd03c2..2f4cbe7439 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/crunchydata.com_client.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/crunchydata.com_client.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/doc.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/doc.go index c7adf35aa6..9036ada13a 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/doc.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/doc.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/doc.go index f1a03c292f..5b67373668 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/doc.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_crunchydata.com_client.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_crunchydata.com_client.go index 6afd73a7ff..df4d5bfbaf 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_crunchydata.com_client.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_crunchydata.com_client.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgcluster.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgcluster.go index 1a9a60e7ac..b36d54c565 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgcluster.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgcluster.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgpolicy.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgpolicy.go index 3220e3239b..574944c5dc 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgpolicy.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgpolicy.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgreplica.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgreplica.go index 91a99e6248..8aea3797d7 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgreplica.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgreplica.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgtask.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgtask.go index 190ba5701c..b1d615a176 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgtask.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/fake/fake_pgtask.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/generated_expansion.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/generated_expansion.go index 3f12094466..c2dead17b5 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/generated_expansion.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/generated_expansion.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgcluster.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgcluster.go index 092525a65e..43b2b53ac2 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgcluster.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgcluster.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgpolicy.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgpolicy.go index bce7004ef1..18dc4c1d7b 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgpolicy.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgpolicy.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgreplica.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgreplica.go index 7f945a2ca4..00073c38c5 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgreplica.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgreplica.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgtask.go b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgtask.go index d596cf277e..d8c2b9b7b7 100644 --- a/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgtask.go +++ b/pkg/generated/clientset/versioned/typed/crunchydata.com/v1/pgtask.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/informers/externalversions/crunchydata.com/interface.go b/pkg/generated/informers/externalversions/crunchydata.com/interface.go index 8aec2edcfb..80a637ab5b 100644 --- a/pkg/generated/informers/externalversions/crunchydata.com/interface.go +++ b/pkg/generated/informers/externalversions/crunchydata.com/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/informers/externalversions/crunchydata.com/v1/interface.go b/pkg/generated/informers/externalversions/crunchydata.com/v1/interface.go index cbaf0ff49a..fe68646ca0 100644 --- a/pkg/generated/informers/externalversions/crunchydata.com/v1/interface.go +++ b/pkg/generated/informers/externalversions/crunchydata.com/v1/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/informers/externalversions/crunchydata.com/v1/pgcluster.go b/pkg/generated/informers/externalversions/crunchydata.com/v1/pgcluster.go index a43c757247..2d874bfd96 100644 --- a/pkg/generated/informers/externalversions/crunchydata.com/v1/pgcluster.go +++ b/pkg/generated/informers/externalversions/crunchydata.com/v1/pgcluster.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/informers/externalversions/crunchydata.com/v1/pgpolicy.go b/pkg/generated/informers/externalversions/crunchydata.com/v1/pgpolicy.go index 2439813877..3e23c5120e 100644 --- a/pkg/generated/informers/externalversions/crunchydata.com/v1/pgpolicy.go +++ b/pkg/generated/informers/externalversions/crunchydata.com/v1/pgpolicy.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/informers/externalversions/crunchydata.com/v1/pgreplica.go b/pkg/generated/informers/externalversions/crunchydata.com/v1/pgreplica.go index 913105590b..8141c330df 100644 --- a/pkg/generated/informers/externalversions/crunchydata.com/v1/pgreplica.go +++ b/pkg/generated/informers/externalversions/crunchydata.com/v1/pgreplica.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/informers/externalversions/crunchydata.com/v1/pgtask.go b/pkg/generated/informers/externalversions/crunchydata.com/v1/pgtask.go index 2c8c4b2744..ad64b2717a 100644 --- a/pkg/generated/informers/externalversions/crunchydata.com/v1/pgtask.go +++ b/pkg/generated/informers/externalversions/crunchydata.com/v1/pgtask.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/informers/externalversions/factory.go b/pkg/generated/informers/externalversions/factory.go index 00e6004f0a..9fb82ec40f 100644 --- a/pkg/generated/informers/externalversions/factory.go +++ b/pkg/generated/informers/externalversions/factory.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/informers/externalversions/generic.go b/pkg/generated/informers/externalversions/generic.go index 7fa4a6e032..0ea3cb4983 100644 --- a/pkg/generated/informers/externalversions/generic.go +++ b/pkg/generated/informers/externalversions/generic.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go b/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go index a7b137f9b6..1ac6cd63dc 100644 --- a/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go +++ b/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/listers/crunchydata.com/v1/expansion_generated.go b/pkg/generated/listers/crunchydata.com/v1/expansion_generated.go index 91a28bb8bd..7cef2e7d88 100644 --- a/pkg/generated/listers/crunchydata.com/v1/expansion_generated.go +++ b/pkg/generated/listers/crunchydata.com/v1/expansion_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/listers/crunchydata.com/v1/pgcluster.go b/pkg/generated/listers/crunchydata.com/v1/pgcluster.go index 410dc333de..e8672cb0f0 100644 --- a/pkg/generated/listers/crunchydata.com/v1/pgcluster.go +++ b/pkg/generated/listers/crunchydata.com/v1/pgcluster.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/listers/crunchydata.com/v1/pgpolicy.go b/pkg/generated/listers/crunchydata.com/v1/pgpolicy.go index 698dfee2f8..44f7b59544 100644 --- a/pkg/generated/listers/crunchydata.com/v1/pgpolicy.go +++ b/pkg/generated/listers/crunchydata.com/v1/pgpolicy.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/listers/crunchydata.com/v1/pgreplica.go b/pkg/generated/listers/crunchydata.com/v1/pgreplica.go index 9a8fa7f89a..f9ea4f7167 100644 --- a/pkg/generated/listers/crunchydata.com/v1/pgreplica.go +++ b/pkg/generated/listers/crunchydata.com/v1/pgreplica.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pkg/generated/listers/crunchydata.com/v1/pgtask.go b/pkg/generated/listers/crunchydata.com/v1/pgtask.go index d88a4ad9f3..50e4245c7d 100644 --- a/pkg/generated/listers/crunchydata.com/v1/pgtask.go +++ b/pkg/generated/listers/crunchydata.com/v1/pgtask.go @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2022 Crunchy Data Solutions, Inc. +Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/pv/create-pv-nfs-label.sh b/pv/create-pv-nfs-label.sh index ec11b1b573..6192373051 100755 --- a/pv/create-pv-nfs-label.sh +++ b/pv/create-pv-nfs-label.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2018 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2018 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/pv/create-pv-nfs-legacy.sh b/pv/create-pv-nfs-legacy.sh index 1f6a381035..514a910dae 100755 --- a/pv/create-pv-nfs-legacy.sh +++ b/pv/create-pv-nfs-legacy.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/pv/create-pv-nfs.sh b/pv/create-pv-nfs.sh index 60dd1f04d6..27c55a9e41 100755 --- a/pv/create-pv-nfs.sh +++ b/pv/create-pv-nfs.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/pv/create-pv.sh b/pv/create-pv.sh index 44fb402031..afc18c28fc 100755 --- a/pv/create-pv.sh +++ b/pv/create-pv.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/pv/delete-pv.sh b/pv/delete-pv.sh index 3b1513184b..c8178402cd 100755 --- a/pv/delete-pv.sh +++ b/pv/delete-pv.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 - 2022 Crunchy Data Solutions, Inc. +# Copyright 2017 - 2023 Crunchy Data Solutions, Inc. # 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 diff --git a/testing/pgo_cli/cluster_annotation_test.go b/testing/pgo_cli/cluster_annotation_test.go index d31159c5e8..a4a85fab97 100644 --- a/testing/pgo_cli/cluster_annotation_test.go +++ b/testing/pgo_cli/cluster_annotation_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_backup_test.go b/testing/pgo_cli/cluster_backup_test.go index 5b7d2e1740..a1e0951c36 100644 --- a/testing/pgo_cli/cluster_backup_test.go +++ b/testing/pgo_cli/cluster_backup_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_cat_test.go b/testing/pgo_cli/cluster_cat_test.go index 123648f5cf..3f9617a4eb 100644 --- a/testing/pgo_cli/cluster_cat_test.go +++ b/testing/pgo_cli/cluster_cat_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_create_test.go b/testing/pgo_cli/cluster_create_test.go index 228177a749..c47b92e4db 100644 --- a/testing/pgo_cli/cluster_create_test.go +++ b/testing/pgo_cli/cluster_create_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_delete_test.go b/testing/pgo_cli/cluster_delete_test.go index 08cf13df79..5f62f24f61 100644 --- a/testing/pgo_cli/cluster_delete_test.go +++ b/testing/pgo_cli/cluster_delete_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_df_test.go b/testing/pgo_cli/cluster_df_test.go index 056c947d3c..dec3ba7ef9 100644 --- a/testing/pgo_cli/cluster_df_test.go +++ b/testing/pgo_cli/cluster_df_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_failover_test.go b/testing/pgo_cli/cluster_failover_test.go index f7dd9fd36a..5628dedf3b 100644 --- a/testing/pgo_cli/cluster_failover_test.go +++ b/testing/pgo_cli/cluster_failover_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_label_test.go b/testing/pgo_cli/cluster_label_test.go index 67d9023604..7c001b52c6 100644 --- a/testing/pgo_cli/cluster_label_test.go +++ b/testing/pgo_cli/cluster_label_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_pgbouncer_test.go b/testing/pgo_cli/cluster_pgbouncer_test.go index 30aebd9571..dc70f490fd 100644 --- a/testing/pgo_cli/cluster_pgbouncer_test.go +++ b/testing/pgo_cli/cluster_pgbouncer_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_policy_test.go b/testing/pgo_cli/cluster_policy_test.go index eb08beee07..1d0cac3bec 100644 --- a/testing/pgo_cli/cluster_policy_test.go +++ b/testing/pgo_cli/cluster_policy_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_pvc_test.go b/testing/pgo_cli/cluster_pvc_test.go index a55d962435..80f9f4abf4 100644 --- a/testing/pgo_cli/cluster_pvc_test.go +++ b/testing/pgo_cli/cluster_pvc_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_reload_test.go b/testing/pgo_cli/cluster_reload_test.go index e0ffd7cc46..0c2a14528b 100644 --- a/testing/pgo_cli/cluster_reload_test.go +++ b/testing/pgo_cli/cluster_reload_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_restart_test.go b/testing/pgo_cli/cluster_restart_test.go index 103ddf5b50..6a1c4eeacf 100644 --- a/testing/pgo_cli/cluster_restart_test.go +++ b/testing/pgo_cli/cluster_restart_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_scale_test.go b/testing/pgo_cli/cluster_scale_test.go index 475b54339d..a2e61fedc8 100644 --- a/testing/pgo_cli/cluster_scale_test.go +++ b/testing/pgo_cli/cluster_scale_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_scaledown_test.go b/testing/pgo_cli/cluster_scaledown_test.go index e8fc1f1a46..f079f35539 100644 --- a/testing/pgo_cli/cluster_scaledown_test.go +++ b/testing/pgo_cli/cluster_scaledown_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_test_test.go b/testing/pgo_cli/cluster_test_test.go index d1f40fe826..af1966ce33 100644 --- a/testing/pgo_cli/cluster_test_test.go +++ b/testing/pgo_cli/cluster_test_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/cluster_user_test.go b/testing/pgo_cli/cluster_user_test.go index 17e416eab3..5cd8bc0cd1 100644 --- a/testing/pgo_cli/cluster_user_test.go +++ b/testing/pgo_cli/cluster_user_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/operator_namespace_test.go b/testing/pgo_cli/operator_namespace_test.go index 32b8f5a85e..fec5241657 100644 --- a/testing/pgo_cli/operator_namespace_test.go +++ b/testing/pgo_cli/operator_namespace_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/operator_rbac_test.go b/testing/pgo_cli/operator_rbac_test.go index 4a18548905..f7fd42d6a5 100644 --- a/testing/pgo_cli/operator_rbac_test.go +++ b/testing/pgo_cli/operator_rbac_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/operator_test.go b/testing/pgo_cli/operator_test.go index 81442de7dc..401a5ac22c 100644 --- a/testing/pgo_cli/operator_test.go +++ b/testing/pgo_cli/operator_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/suite_helpers_test.go b/testing/pgo_cli/suite_helpers_test.go index f0d8a68448..8ae205bb94 100644 --- a/testing/pgo_cli/suite_helpers_test.go +++ b/testing/pgo_cli/suite_helpers_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/suite_pgo_cmd_test.go b/testing/pgo_cli/suite_pgo_cmd_test.go index f19fb4a879..1721c702e1 100644 --- a/testing/pgo_cli/suite_pgo_cmd_test.go +++ b/testing/pgo_cli/suite_pgo_cmd_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 diff --git a/testing/pgo_cli/suite_test.go b/testing/pgo_cli/suite_test.go index bca4b81a5e..adbbb5971f 100644 --- a/testing/pgo_cli/suite_test.go +++ b/testing/pgo_cli/suite_test.go @@ -1,7 +1,7 @@ package pgo_cli_test /* - Copyright 2020 - 2022 Crunchy Data Solutions, Inc. + Copyright 2020 - 2023 Crunchy Data Solutions, Inc. 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 From c7114659425ac9ee70c4da1d7485cc88a024d716 Mon Sep 17 00:00:00 2001 From: ValClarkson Date: Mon, 27 Feb 2023 16:10:21 -0500 Subject: [PATCH 269/276] Release prep for v4.6.10 Issue: [sc-17754] --- Makefile | 4 +- README.md | 2 +- bin/push-ccp-to-gcr.sh | 2 +- conf/postgres-operator/pgo.yaml | 4 +- docs/config.toml | 10 ++-- docs/content/Configuration/compatibility.md | 4 ++ docs/content/releases/4.6.10.md | 21 ++++++++ installers/ansible/README.md | 2 +- installers/ansible/values.yaml | 6 +-- installers/gcp-marketplace/Makefile | 2 +- installers/gcp-marketplace/README.md | 2 +- installers/gcp-marketplace/values.yaml | 6 +-- installers/helm/Chart.yaml | 2 +- installers/helm/values.yaml | 6 +-- installers/kubectl/client-setup.sh | 2 +- .../kubectl/postgres-operator-ocp311.yml | 8 +-- installers/kubectl/postgres-operator.yml | 8 +-- installers/metrics/ansible/README.md | 2 +- installers/metrics/helm/Chart.yaml | 2 +- installers/metrics/helm/helm_template.yaml | 2 +- installers/metrics/helm/values.yaml | 2 +- .../postgres-operator-metrics-ocp311.yml | 2 +- .../kubectl/postgres-operator-metrics.yml | 2 +- installers/olm/Makefile | 4 +- installers/olm/description.openshift.md | 2 +- installers/olm/description.upstream.md | 2 +- pkg/apis/crunchydata.com/v1/doc.go | 49 +++++++++---------- pkg/apiservermsgs/common.go | 2 +- redhat/atomic/help.1 | 2 +- redhat/atomic/help.md | 2 +- 30 files changed, 95 insertions(+), 71 deletions(-) create mode 100644 docs/content/releases/4.6.10.md diff --git a/Makefile b/Makefile index 49127a7172..05e4557ee8 100644 --- a/Makefile +++ b/Makefile @@ -4,9 +4,9 @@ PGOROOT ?= $(CURDIR) PGO_BASEOS ?= ubi8 PGO_IMAGE_PREFIX ?= crunchydata PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) -PGO_VERSION ?= 4.6.9 +PGO_VERSION ?= 4.6.10 PGO_PG_VERSION ?= 13 -PGO_PG_FULLVERSION ?= 13.9 +PGO_PG_FULLVERSION ?= 13.10 PGO_BACKREST_VERSION ?= 2.31 PACKAGER ?= yum diff --git a/README.md b/README.md index f114f444ed..fc8056939f 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ to start as quickly as: ```shell kubectl create namespace pgo -kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.9/installers/kubectl/postgres-operator.yml +kubectl apply -f https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.6.10/installers/kubectl/postgres-operator.yml ``` Otherwise, we highly recommend following the instructions from our [Quickstart](https://access.crunchydata.com/documentation/postgres-operator/latest/quickstart/). diff --git a/bin/push-ccp-to-gcr.sh b/bin/push-ccp-to-gcr.sh index 11f701a87e..f90441a85a 100755 --- a/bin/push-ccp-to-gcr.sh +++ b/bin/push-ccp-to-gcr.sh @@ -16,7 +16,7 @@ GCR_IMAGE_PREFIX=gcr.io/crunchy-dev-test CCP_IMAGE_PREFIX=crunchydata -CCP_IMAGE_TAG=ubi8-13.9-4.6.9 +CCP_IMAGE_TAG=ubi8-13.10-4.6.10 IMAGES=( crunchy-prometheus diff --git a/conf/postgres-operator/pgo.yaml b/conf/postgres-operator/pgo.yaml index 69dce2c695..a5289e1f6e 100644 --- a/conf/postgres-operator/pgo.yaml +++ b/conf/postgres-operator/pgo.yaml @@ -2,7 +2,7 @@ Cluster: CCPImagePrefix: registry.developers.crunchydata.com/crunchydata Metrics: false Badger: false - CCPImageTag: ubi8-13.9-4.6.9 + CCPImageTag: ubi8-13.10-4.6.10 Port: 5432 PGBadgerPort: 10000 ExporterPort: 9187 @@ -80,4 +80,4 @@ Storage: Pgo: Audit: false PGOImagePrefix: registry.developers.crunchydata.com/crunchydata - PGOImageTag: ubi8-4.6.9 + PGOImageTag: ubi8-4.6.10 diff --git a/docs/config.toml b/docs/config.toml index 048660a1b3..e150db3704 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -25,11 +25,11 @@ disableNavChevron = false # set true to hide next/prev chevron, default is false highlightClientSide = false # set true to use highlight.pack.js instead of the default hugo chroma highlighter menushortcutsnewtab = true # set true to open shortcuts links to a new tab/window enableGitInfo = true -operatorVersion = "4.6.9" -postgresVersion = "13.9" -postgresVersion13 = "13.9" -postgresVersion12 = "12.13" -postgresVersion11 = "11.18" +operatorVersion = "4.6.10" +postgresVersion = "13.10" +postgresVersion13 = "13.10" +postgresVersion12 = "12.14" +postgresVersion11 = "11.19" postgisVersion = "3.0" ubiBase = "ubi8" diff --git a/docs/content/Configuration/compatibility.md b/docs/content/Configuration/compatibility.md index 28576060cb..c001104811 100644 --- a/docs/content/Configuration/compatibility.md +++ b/docs/content/Configuration/compatibility.md @@ -12,6 +12,10 @@ version dependencies between the two projects. Below are the operator releases a | Operator Release | Container Release | Postgres | PgBackrest Version |:----------|:-------------|:------------|:-------------- +| 4.6.10 | 4.6.10 | 13.10 | 2.31 | +|||12.14|2.31| +|||11.19|2.31| +|||| | 4.6.9 | 4.6.9 | 13.9 | 2.31 | |||12.13|2.31| |||11.18|2.31| diff --git a/docs/content/releases/4.6.10.md b/docs/content/releases/4.6.10.md new file mode 100644 index 0000000000..f4796c183b --- /dev/null +++ b/docs/content/releases/4.6.10.md @@ -0,0 +1,21 @@ +--- +title: "4.6.9" +date: +draft: false +weight: 50 +--- + +Crunchy Data announces the release of PGO, the Postgres Operator 4.6.10. + +The PostgreSQL Operator is released in conjunction with the [Crunchy Container Suite](https://github.com/CrunchyData/crunchy-containers/). + +Crunchy Postgres for Kubernetes 4.6.10 includes the following software versions upgrades: + +PostgreSQL versions 13.10, 12.14 and 11.19 are now available. +The `orafce` extension is now at version 4.1.1. +The `pg_partman` extension is now at version 4.7.2. +The `set_user` extension is now at version 4.0.1. +The `TimescaleDB` extension is now at version 2.9.2. + +## Fixes +- The `crunchy-pgadmin` container for UBI 7 and CentOS 7 no longer throws an error when starting. \ No newline at end of file diff --git a/installers/ansible/README.md b/installers/ansible/README.md index 53e7de13d5..836da40b76 100644 --- a/installers/ansible/README.md +++ b/installers/ansible/README.md @@ -4,7 +4,7 @@ PGO: The Postgres Operator from Crunchy Data

-Latest Release: 4.6.9 +Latest Release: 4.6.10 ## General diff --git a/installers/ansible/values.yaml b/installers/ansible/values.yaml index 062f93c08e..50ba5c8055 100644 --- a/installers/ansible/values.yaml +++ b/installers/ansible/values.yaml @@ -17,7 +17,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "ubi8-13.9-4.6.9" +ccp_image_tag: "ubi8-13.10-4.6.10" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -49,14 +49,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.9" +pgo_client_version: "4.6.10" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "ubi8-4.6.9" +pgo_image_tag: "ubi8-4.6.10" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/gcp-marketplace/Makefile b/installers/gcp-marketplace/Makefile index f51929da83..e5a0fca7a7 100644 --- a/installers/gcp-marketplace/Makefile +++ b/installers/gcp-marketplace/Makefile @@ -6,7 +6,7 @@ MARKETPLACE_TOOLS ?= gcr.io/cloud-marketplace-tools/k8s/dev:$(MARKETPLACE_VERSIO MARKETPLACE_VERSION ?= 0.9.4 KUBECONFIG ?= $(HOME)/.kube/config PARAMETERS ?= {} -PGO_VERSION ?= 4.6.9 +PGO_VERSION ?= 4.6.10 IMAGE_BUILD_ARGS = --build-arg MARKETPLACE_VERSION='$(MARKETPLACE_VERSION)' \ --build-arg PGO_VERSION='$(PGO_VERSION)' diff --git a/installers/gcp-marketplace/README.md b/installers/gcp-marketplace/README.md index 1307eb5345..62255ffe3f 100644 --- a/installers/gcp-marketplace/README.md +++ b/installers/gcp-marketplace/README.md @@ -59,7 +59,7 @@ Google Cloud Marketplace. ```shell IMAGE_REPOSITORY=gcr.io/crunchydata-public/postgres-operator - export PGO_VERSION=4.6.9 + export PGO_VERSION=4.6.10 export INSTALLER_IMAGE=${IMAGE_REPOSITORY}/deployer:${PGO_VERSION} export OPERATOR_IMAGE=${IMAGE_REPOSITORY}:${PGO_VERSION} export OPERATOR_IMAGE_API=${IMAGE_REPOSITORY}/pgo-apiserver:${PGO_VERSION} diff --git a/installers/gcp-marketplace/values.yaml b/installers/gcp-marketplace/values.yaml index 5ddfc26c50..abcba17b68 100644 --- a/installers/gcp-marketplace/values.yaml +++ b/installers/gcp-marketplace/values.yaml @@ -10,7 +10,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "ubi8-13.9-4.6.9" +ccp_image_tag: "ubi8-13.10-4.6.10" create_rbac: "true" db_name: "" db_password_age_days: "0" @@ -32,9 +32,9 @@ pgo_admin_role_name: "pgoadmin" pgo_admin_username: "admin" pgo_client_container_install: "false" pgo_client_install: 'false' -pgo_client_version: "4.6.9" +pgo_client_version: "4.6.10" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "ubi8-4.6.9" +pgo_image_tag: "ubi8-4.6.10" pgo_installation_name: '${OPERATOR_NAME}' pgo_operator_namespace: '${OPERATOR_NAMESPACE}' scheduler_timeout: "3600" diff --git a/installers/helm/Chart.yaml b/installers/helm/Chart.yaml index 2baf87773b..a6df235a01 100644 --- a/installers/helm/Chart.yaml +++ b/installers/helm/Chart.yaml @@ -3,7 +3,7 @@ name: postgres-operator description: 'PGO: The Postgres Operator from Crunchy Data Helm Chart for Kubernetes' type: application version: 0.2.0 -appVersion: 4.6.9 +appVersion: 4.6.10 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/docs/static/logos/pgo.svg keywords: diff --git a/installers/helm/values.yaml b/installers/helm/values.yaml index cc0c4e5753..c80b1d2fef 100644 --- a/installers/helm/values.yaml +++ b/installers/helm/values.yaml @@ -37,7 +37,7 @@ badger: "false" ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" -ccp_image_tag: "ubi8-13.9-4.6.9" +ccp_image_tag: "ubi8-13.10-4.6.10" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -69,14 +69,14 @@ pgo_apiserver_url: "https://postgres-operator" pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" -pgo_client_version: "4.6.9" +pgo_client_version: "4.6.10" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" -pgo_image_tag: "ubi8-4.6.9" +pgo_image_tag: "ubi8-4.6.10" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" diff --git a/installers/kubectl/client-setup.sh b/installers/kubectl/client-setup.sh index 7eae3218d7..e1431abd20 100755 --- a/installers/kubectl/client-setup.sh +++ b/installers/kubectl/client-setup.sh @@ -14,7 +14,7 @@ # This script should be run after the operator has been deployed PGO_OPERATOR_NAMESPACE="${PGO_OPERATOR_NAMESPACE:-pgo}" PGO_USER_ADMIN="${PGO_USER_ADMIN:-pgouser-admin}" -PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.9}" +PGO_CLIENT_VERSION="${PGO_CLIENT_VERSION:-v4.6.10}" PGO_CLIENT_URL="https://github.com/CrunchyData/postgres-operator/releases/download/${PGO_CLIENT_VERSION}" PGO_CMD="${PGO_CMD-kubectl}" diff --git a/installers/kubectl/postgres-operator-ocp311.yml b/installers/kubectl/postgres-operator-ocp311.yml index 56d86b49cf..d44ce571fe 100644 --- a/installers/kubectl/postgres-operator-ocp311.yml +++ b/installers/kubectl/postgres-operator-ocp311.yml @@ -50,7 +50,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "ubi8-13.9-4.6.9" + ccp_image_tag: "ubi8-13.10-4.6.10" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -83,14 +83,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.9" + pgo_client_version: "4.6.10" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "ubi8-4.6.9" + pgo_image_tag: "ubi8-4.6.10" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -171,7 +171,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.9 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.10 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/kubectl/postgres-operator.yml b/installers/kubectl/postgres-operator.yml index 91600bd766..85fd290f9a 100644 --- a/installers/kubectl/postgres-operator.yml +++ b/installers/kubectl/postgres-operator.yml @@ -145,7 +145,7 @@ data: ccp_image_prefix: "registry.developers.crunchydata.com/crunchydata" ccp_image_pull_secret: "" ccp_image_pull_secret_manifest: "" - ccp_image_tag: "ubi8-13.9-4.6.9" + ccp_image_tag: "ubi8-13.10-4.6.10" create_rbac: "true" crunchy_debug: "false" db_name: "" @@ -178,14 +178,14 @@ data: pgo_client_cert_secret: "pgo.tls" pgo_client_container_install: "false" pgo_client_install: "true" - pgo_client_version: "4.6.9" + pgo_client_version: "4.6.10" pgo_cluster_admin: "false" pgo_disable_eventing: "false" pgo_disable_tls: "false" pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" pgo_image_pull_secret: "" pgo_image_pull_secret_manifest: "" - pgo_image_tag: "ubi8-4.6.9" + pgo_image_tag: "ubi8-4.6.10" pgo_installation_name: "devtest" pgo_noauth_routes: "" pgo_operator_namespace: "pgo" @@ -281,7 +281,7 @@ spec: restartPolicy: Never containers: - name: pgo-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.9 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.10 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/ansible/README.md b/installers/metrics/ansible/README.md index a89f03e1b6..c14ed41eb7 100644 --- a/installers/metrics/ansible/README.md +++ b/installers/metrics/ansible/README.md @@ -4,7 +4,7 @@ Crunchy Data

-Latest Release: 4.6.9 +Latest Release: 4.6.10 ## General diff --git a/installers/metrics/helm/Chart.yaml b/installers/metrics/helm/Chart.yaml index 9adcf6a15e..b0133bdc6c 100644 --- a/installers/metrics/helm/Chart.yaml +++ b/installers/metrics/helm/Chart.yaml @@ -3,6 +3,6 @@ name: postgres-operator-monitoring description: Install for Crunchy PostgreSQL Operator Monitoring type: application version: 0.2.0 -appVersion: 4.6.9 +appVersion: 4.6.10 home: https://github.com/CrunchyData/postgres-operator icon: https://github.com/CrunchyData/postgres-operator/raw/master/docs/static/logos/pgo.svg diff --git a/installers/metrics/helm/helm_template.yaml b/installers/metrics/helm/helm_template.yaml index 663e9214eb..63647f1f90 100644 --- a/installers/metrics/helm/helm_template.yaml +++ b/installers/metrics/helm/helm_template.yaml @@ -20,5 +20,5 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "ubi8-4.6.9" +pgo_image_tag: "ubi8-4.6.10" diff --git a/installers/metrics/helm/values.yaml b/installers/metrics/helm/values.yaml index 4667d53c45..990f7343ee 100644 --- a/installers/metrics/helm/values.yaml +++ b/installers/metrics/helm/values.yaml @@ -20,7 +20,7 @@ serviceAccount: # the image prefix and tag to use for the 'pgo-deployer' container pgo_image_prefix: "registry.developers.crunchydata.com/crunchydata" -pgo_image_tag: "ubi8-4.6.9" +pgo_image_tag: "ubi8-4.6.10" # ===================== # Configuration Options diff --git a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml index a95f6fe403..afdd9dc133 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics-ocp311.yml @@ -101,7 +101,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.9 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.10 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/metrics/kubectl/postgres-operator-metrics.yml b/installers/metrics/kubectl/postgres-operator-metrics.yml index 115362dcc6..dabb857554 100644 --- a/installers/metrics/kubectl/postgres-operator-metrics.yml +++ b/installers/metrics/kubectl/postgres-operator-metrics.yml @@ -171,7 +171,7 @@ spec: restartPolicy: Never containers: - name: pgo-metrics-deploy - image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.9 + image: registry.developers.crunchydata.com/crunchydata/pgo-deployer:ubi8-4.6.10 imagePullPolicy: IfNotPresent env: - name: DEPLOY_ACTION diff --git a/installers/olm/Makefile b/installers/olm/Makefile index 63e94b47ee..783acbc046 100644 --- a/installers/olm/Makefile +++ b/installers/olm/Makefile @@ -2,7 +2,7 @@ .SUFFIXES: CCP_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -CCP_PG_FULLVERSION ?= 13.9 +CCP_PG_FULLVERSION ?= 13.10 CCP_POSTGIS_VERSION ?= 3.0 CONTAINER ?= docker KUBECONFIG ?= $(HOME)/.kube/config @@ -11,7 +11,7 @@ OLM_TOOLS ?= registry.localhost:5000/postgres-operator-olm-tools:$(OLM_SDK_VERSI OLM_VERSION ?= 0.15.1 PGO_BASEOS ?= ubi8 PGO_IMAGE_PREFIX ?= registry.developers.crunchydata.com/crunchydata -PGO_VERSION ?= 4.6.9 +PGO_VERSION ?= 4.6.10 PGO_IMAGE_TAG ?= $(PGO_BASEOS)-$(PGO_VERSION) CCP_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(PGO_VERSION) CCP_POSTGIS_IMAGE_TAG ?= $(PGO_BASEOS)-$(CCP_PG_FULLVERSION)-$(CCP_POSTGIS_VERSION)-$(PGO_VERSION) diff --git a/installers/olm/description.openshift.md b/installers/olm/description.openshift.md index 6b54bbbacf..dcb3116fff 100644 --- a/installers/olm/description.openshift.md +++ b/installers/olm/description.openshift.md @@ -188,7 +188,7 @@ spec: annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: ${cluster_image_prefix} - ccpimagetag: ubi8-13.9-${PGO_VERSION} + ccpimagetag: ubi8-13.10-${PGO_VERSION} clustername: ${pgo_cluster_name} database: ${pgo_cluster_name} exporterport: "9187" diff --git a/installers/olm/description.upstream.md b/installers/olm/description.upstream.md index cb7a2c9771..df2e148fd4 100644 --- a/installers/olm/description.upstream.md +++ b/installers/olm/description.upstream.md @@ -168,7 +168,7 @@ spec: annotations: {} ccpimage: crunchy-postgres-ha ccpimageprefix: ${cluster_image_prefix} - ccpimagetag: ubi8-13.9-${PGO_VERSION} + ccpimagetag: ubi8-13.10-${PGO_VERSION} clustername: ${pgo_cluster_name} database: ${pgo_cluster_name} exporterport: "9187" diff --git a/pkg/apis/crunchydata.com/v1/doc.go b/pkg/apis/crunchydata.com/v1/doc.go index 554ad16831..9482c53360 100644 --- a/pkg/apis/crunchydata.com/v1/doc.go +++ b/pkg/apis/crunchydata.com/v1/doc.go @@ -3,7 +3,6 @@ Crunchy PostgreSQL Operator API The Crunchy PostgreSQL Operator API defines HTTP(S) interactions with the Crunchy PostgreSQL Operator. - ## Direct API Calls The API can also be accessed by interacting directly with the API server. This @@ -15,7 +14,6 @@ that includes the content type and the `--insecure` flag. These flags will be the same for all of your interactions with the API server and can be seen in the following examples. - ###### Get API Server Version The most basic example of this interaction is getting the version of the API @@ -53,15 +51,14 @@ cluster. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ -POST --data \ - '{"ClientVersion":"4.6.9", - "Namespace":"pgouser1", - "Name":"mycluster", + POST --data \ + '{"ClientVersion":"4.6.10", + "Namespace":"pgouser1", + "Name":"mycluster", + $PGO_APISERVER_URL/clusters ``` - - ###### Show and Delete Cluster The last two examples show you how to `show` and `delete` a cluster. Notice how instead of passing `"Name":"mycluster"` you pass `"Clustername":"mycluster" @@ -73,10 +70,11 @@ show all of the clusters that are in the given namespace. curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ -POST --data \ - '{"ClientVersion":"4.6.9", - "Namespace":"pgouser1", - "Clustername":"mycluster"}' \ + POST --data \ + '{"ClientVersion":"4.6.10", + "Namespace":"pgouser1", + "Clustername":"mycluster"}' \ + $PGO_APISERVER_URL/showclusters ``` @@ -84,25 +82,26 @@ $PGO_APISERVER_URL/showclusters curl --cacert $PGO_CA_CERT --key $PGO_CLIENT_KEY --cert $PGO_CA_CERT -u \ admin:examplepassword -H "Content-Type:application/json" --insecure -X \ -POST --data \ - '{"ClientVersion":"4.6.9", - "Namespace":"pgouser1", - "Clustername":"mycluster"}' \ + POST --data \ + '{"ClientVersion":"4.6.10", + "Namespace":"pgouser1", + "Clustername":"mycluster"}' \ + $PGO_APISERVER_URL/clustersdelete ``` - Schemes: http, https - BasePath: / - Version: 4.6.9 - License: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0 - Contact: Crunchy Data https://www.crunchydata.com/ + Schemes: http, https + BasePath: / + Version: 4.6.10 + License: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0 + Contact: Crunchy Data https://www.crunchydata.com/ - Consumes: - - application/json + Consumes: + - application/json - Produces: - - application/json + Produces: + - application/json swagger:meta */ diff --git a/pkg/apiservermsgs/common.go b/pkg/apiservermsgs/common.go index 8883c828d2..49c2b295b4 100644 --- a/pkg/apiservermsgs/common.go +++ b/pkg/apiservermsgs/common.go @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -const PGO_VERSION = "4.6.9" +const PGO_VERSION = "4.6.10" // Ok status const Ok = "ok" diff --git a/redhat/atomic/help.1 b/redhat/atomic/help.1 index edb99b1d09..65572d4c53 100644 --- a/redhat/atomic/help.1 +++ b/redhat/atomic/help.1 @@ -56,4 +56,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa \fB\fCRelease=\fR .PP -The specific release number of the container. For example, Release="4.6.9" +The specific release number of the container. For example, Release="4.6.10" diff --git a/redhat/atomic/help.md b/redhat/atomic/help.md index 5b1a898e4b..52f10b95be 100644 --- a/redhat/atomic/help.md +++ b/redhat/atomic/help.md @@ -45,4 +45,4 @@ The Red Hat Enterprise Linux version from which the container was built. For exa `Release=` -The specific release number of the container. For example, Release="4.6.9" +The specific release number of the container. For example, Release="4.6.10" From f629e9c4c4c440ad645869d6e2ae2e04a91abaae Mon Sep 17 00:00:00 2001 From: jmckulk Date: Tue, 28 Feb 2023 17:50:16 -0500 Subject: [PATCH 270/276] update some old version references --- docs/content/releases/4.6.10.md | 2 +- examples/create-by-resource/fromcrd.json | 6 +++--- examples/envs.sh | 2 +- examples/helm/README.md | 2 +- examples/helm/postgres/Chart.yaml | 2 +- examples/helm/postgres/templates/pgcluster.yaml | 2 +- examples/helm/postgres/values.yaml | 2 +- examples/kustomize/createcluster/README.md | 16 ++++++++-------- .../kustomize/createcluster/base/pgcluster.yaml | 6 +++--- .../overlay/staging/hippo-rpl1-pgreplica.yaml | 2 +- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/content/releases/4.6.10.md b/docs/content/releases/4.6.10.md index f4796c183b..e038d76cb0 100644 --- a/docs/content/releases/4.6.10.md +++ b/docs/content/releases/4.6.10.md @@ -1,5 +1,5 @@ --- -title: "4.6.9" +title: "4.6.10" date: draft: false weight: 50 diff --git a/examples/create-by-resource/fromcrd.json b/examples/create-by-resource/fromcrd.json index 77462bc30f..2184f60181 100644 --- a/examples/create-by-resource/fromcrd.json +++ b/examples/create-by-resource/fromcrd.json @@ -10,7 +10,7 @@ "deployment-name": "fromcrd", "name": "fromcrd", "pg-cluster": "fromcrd", - "pgo-version": "4.6.7", + "pgo-version": "4.6.10", "pgouser": "pgoadmin" }, "name": "fromcrd", @@ -45,7 +45,7 @@ "supplementalgroups": "" }, "ccpimage": "crunchy-postgres-ha", - "ccpimagetag": "ubi8-13.7-4.6.7", + "ccpimagetag": "ubi8-13.10-4.6.10", "clustername": "fromcrd", "database": "userdb", "exporterport": "9187", @@ -60,7 +60,7 @@ "port": "5432", "user": "testuser", "userlabels": { - "pgo-version": "4.6.7" + "pgo-version": "4.6.10" } } } diff --git a/examples/envs.sh b/examples/envs.sh index 58849805c3..3824b69682 100644 --- a/examples/envs.sh +++ b/examples/envs.sh @@ -20,7 +20,7 @@ export PGO_CONF_DIR=$PGOROOT/installers/ansible/roles/pgo-operator/files # the version of the Operator you run is set by these vars export PGO_IMAGE_PREFIX=registry.developers.crunchydata.com/crunchydata export PGO_BASEOS=ubi8 -export PGO_VERSION=4.6.7 +export PGO_VERSION=4.6.10 export PGO_IMAGE_TAG=$PGO_BASEOS-$PGO_VERSION # for setting the pgo apiserver port, disabling TLS or not verifying TLS diff --git a/examples/helm/README.md b/examples/helm/README.md index 2d6dca35f1..fc2a0adec2 100644 --- a/examples/helm/README.md +++ b/examples/helm/README.md @@ -64,7 +64,7 @@ The following values can also be set: - `ha`: Whether or not to deploy a high availability PostgreSQL cluster. Can be either `true` or `false`, defaults to `false`. - `imagePrefix`: The prefix of the container images to use for this PostgreSQL cluster. Default to `registry.developers.crunchydata.com/crunchydata`. - `image`: The name of the container image to use for the PostgreSQL cluster. Defaults to `crunchy-postgres-ha`. -- `imageTag`: The container image tag to use. Defaults to `ubi8-13.7-4.6.7`. +- `imageTag`: The container image tag to use. Defaults to `ubi8-13.10-4.6.10`. - `memory`: The memory limit for the PostgreSQL cluster. Follows standard Kubernetes formatting. - `monitoring`: Whether or not to enable monitoring / metrics collection for this PostgreSQL instance. Can either be `true` or `false`, defaults to `false`. diff --git a/examples/helm/postgres/Chart.yaml b/examples/helm/postgres/Chart.yaml index 1c539ae345..b4b3fcecbc 100644 --- a/examples/helm/postgres/Chart.yaml +++ b/examples/helm/postgres/Chart.yaml @@ -20,4 +20,4 @@ version: 0.2.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. -appVersion: 4.6.7 +appVersion: 4.6.10 diff --git a/examples/helm/postgres/templates/pgcluster.yaml b/examples/helm/postgres/templates/pgcluster.yaml index e5f465fad8..dae6801f94 100644 --- a/examples/helm/postgres/templates/pgcluster.yaml +++ b/examples/helm/postgres/templates/pgcluster.yaml @@ -28,7 +28,7 @@ spec: storagetype: dynamic ccpimage: {{ .Values.image | default "crunchy-postgres-ha" | quote }} ccpimageprefix: {{ .Values.imagePrefix | default "registry.developers.crunchydata.com/crunchydata" | quote }} - ccpimagetag: {{ .Values.imageTag | default "ubi8-13.7-4.6.7" | quote }} + ccpimagetag: {{ .Values.imageTag | default "ubi8-13.10-4.6.10" | quote }} clustername: {{ .Values.name | quote }} database: {{ .Values.name | quote }} {{- if .Values.monitoring }} diff --git a/examples/helm/postgres/values.yaml b/examples/helm/postgres/values.yaml index 5d401ad8d5..cd00be0662 100644 --- a/examples/helm/postgres/values.yaml +++ b/examples/helm/postgres/values.yaml @@ -10,5 +10,5 @@ password: W4tch0ut4hippo$ # ha: true # imagePrefix: registry.developers.crunchydata.com/crunchydata # image: crunchy-postgres-ha -# imageTag: ubi8-13.7-4.6.7 +# imageTag: ubi8-13.10-4.6.10 # memory: 1Gi diff --git a/examples/kustomize/createcluster/README.md b/examples/kustomize/createcluster/README.md index 415cb919df..f3a0ee5e46 100644 --- a/examples/kustomize/createcluster/README.md +++ b/examples/kustomize/createcluster/README.md @@ -44,13 +44,13 @@ pgo show cluster hippo -n pgo ``` You will see something like this if successful: ``` -cluster : hippo (crunchy-postgres-ha:ubi8-13.7-4.6.7) +cluster : hippo (crunchy-postgres-ha:ubi8-13.10-4.6.10) pod : hippo-8fb6bd96-j87wq (Running) on gke-xxxx-default-pool-38e946bd-257w (1/1) (primary) pvc: hippo (1Gi) deployment : hippo deployment : hippo-backrest-shared-repo service : hippo - ClusterIP (10.0.56.86) - Ports (2022/TCP, 5432/TCP) - labels : pgo-version=4.6.7 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.10 name=hippo crunchy-pgha-scope=hippo deployment-name=hippo pg-cluster=hippo pgouser=admin vendor=crunchydata ``` Feel free to run other pgo cli commands on the hippo cluster @@ -79,7 +79,7 @@ pgo show cluster dev-hippo -n pgo ``` You will see something like this if successful: ``` -cluster : dev-hippo (crunchy-postgres-ha:ubi8-13.7-4.6.7) +cluster : dev-hippo (crunchy-postgres-ha:ubi8-13.10-4.6.10) pod : dev-hippo-588d4cb746-bwrxb (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: dev-hippo (1Gi) deployment : dev-hippo @@ -87,7 +87,7 @@ cluster : dev-hippo (crunchy-postgres-ha:ubi8-13.7-4.6.7) deployment : dev-hippo-pgbouncer service : dev-hippo - ClusterIP (10.0.62.87) - Ports (2022/TCP, 5432/TCP) service : dev-hippo-pgbouncer - ClusterIP (10.0.48.120) - Ports (5432/TCP) - labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.6.7 pgouser=admin + labels : crunchy-pgha-scope=dev-hippo name=dev-hippo pg-cluster=dev-hippo vendor=crunchydata deployment-name=dev-hippo environment=development pgo-version=4.6.10 pgouser=admin ``` #### staging The staging overlay will deploy a crunchy postgreSQL cluster with 2 replica's with annotations added @@ -113,7 +113,7 @@ pgo show cluster staging-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : staging-hippo (crunchy-postgres-ha:ubi8-13.7-4.6.7) +cluster : staging-hippo (crunchy-postgres-ha:ubi8-13.10-4.6.10) pod : staging-hippo-85cf6dcb65-9h748 (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (primary) pvc: staging-hippo (1Gi) pod : staging-hippo-lnxw-cf47d8c8b-6r4wn (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (replica) @@ -128,7 +128,7 @@ cluster : staging-hippo (crunchy-postgres-ha:ubi8-13.7-4.6.7) service : staging-hippo-replica - ClusterIP (10.0.56.57) - Ports (2022/TCP, 5432/TCP) pgreplica : staging-hippo-lnxw pgreplica : staging-hippo-rpl1 - labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.6.7 pgouser=admin vendor=crunchydata + labels : deployment-name=staging-hippo environment=staging name=staging-hippo crunchy-pgha-scope=staging-hippo pg-cluster=staging-hippo pgo-version=4.6.10 pgouser=admin vendor=crunchydata ``` #### production @@ -154,7 +154,7 @@ pgo show cluster prod-hippo -n pgo ``` You will see something like this if successful, (Notice one of the replicas is a different size): ``` -cluster : prod-hippo (crunchy-postgres-ha:ubi8-13.7-4.6.7) +cluster : prod-hippo (crunchy-postgres-ha:ubi8-13.10-4.6.10) pod : prod-hippo-5d6dd46497-rr67c (Running) on gke-xxxx-default-pool-21b7282d-rqkj (1/1) (primary) pvc: prod-hippo (1Gi) pod : prod-hippo-flty-84d97c8769-2pzbh (Running) on gke-xxxx-default-pool-95cba91c-0ppp (1/1) (replica) @@ -165,7 +165,7 @@ cluster : prod-hippo (crunchy-postgres-ha:ubi8-13.7-4.6.7) service : prod-hippo - ClusterIP (10.0.56.18) - Ports (2022/TCP, 5432/TCP) service : prod-hippo-replica - ClusterIP (10.0.56.101) - Ports (2022/TCP, 5432/TCP) pgreplica : prod-hippo-flty - labels : pgo-version=4.6.7 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata + labels : pgo-version=4.6.10 deployment-name=prod-hippo environment=production pg-cluster=prod-hippo crunchy-pgha-scope=prod-hippo name=prod-hippo pgouser=admin vendor=crunchydata ``` ### Delete the clusters To delete the clusters run the following pgo cli commands diff --git a/examples/kustomize/createcluster/base/pgcluster.yaml b/examples/kustomize/createcluster/base/pgcluster.yaml index fa1d0f4758..2f91873da9 100644 --- a/examples/kustomize/createcluster/base/pgcluster.yaml +++ b/examples/kustomize/createcluster/base/pgcluster.yaml @@ -10,7 +10,7 @@ metadata: deployment-name: hippo name: hippo pg-cluster: hippo - pgo-version: 4.6.7 + pgo-version: 4.6.10 pgouser: admin name: hippo namespace: pgo @@ -46,7 +46,7 @@ spec: postgres: {} ccpimage: crunchy-postgres-ha ccpimageprefix: registry.developers.crunchydata.com/crunchydata - ccpimagetag: ubi8-13.7-4.6.7 + ccpimagetag: ubi8-13.10-4.6.10 clustername: hippo customconfig: "" database: hippo @@ -69,4 +69,4 @@ spec: port: "5432" user: hippo userlabels: - pgo-version: 4.6.7 + pgo-version: 4.6.10 diff --git a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml index 394642f1a2..4a9d910566 100644 --- a/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml +++ b/examples/kustomize/createcluster/overlay/staging/hippo-rpl1-pgreplica.yaml @@ -20,4 +20,4 @@ spec: storagetype: dynamic supplementalgroups: "" userlabels: - pgo-version: 4.6.7 + pgo-version: 4.6.10 From fb921c2d78c6452ba9cbb84db9fb39e133211e42 Mon Sep 17 00:00:00 2001 From: Drew Sessler Date: Fri, 3 Mar 2023 01:15:44 +0000 Subject: [PATCH 271/276] Add pgbouncer bump to release notes. [sc-18203] --- docs/content/releases/4.6.10.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/content/releases/4.6.10.md b/docs/content/releases/4.6.10.md index e038d76cb0..d756555396 100644 --- a/docs/content/releases/4.6.10.md +++ b/docs/content/releases/4.6.10.md @@ -11,11 +11,12 @@ The PostgreSQL Operator is released in conjunction with the [Crunchy Container S Crunchy Postgres for Kubernetes 4.6.10 includes the following software versions upgrades: -PostgreSQL versions 13.10, 12.14 and 11.19 are now available. -The `orafce` extension is now at version 4.1.1. -The `pg_partman` extension is now at version 4.7.2. -The `set_user` extension is now at version 4.0.1. -The `TimescaleDB` extension is now at version 2.9.2. +- [PostgreSQL](https://www.postgresql.org) versions 13.10, 12.14 and 11.19 are now available. +- [PgBouncer](https://www.pgbouncer.org/) is now at version 1.18. +- The `orafce` extension is now at version 4.1.1. +- The `pg_partman` extension is now at version 4.7.2. +- The `set_user` extension is now at version 4.0.1. +- The `TimescaleDB` extension is now at version 2.9.2. ## Fixes -- The `crunchy-pgadmin` container for UBI 7 and CentOS 7 no longer throws an error when starting. \ No newline at end of file +- The `crunchy-pgadmin` container for UBI 7 and CentOS 7 no longer throws an error when starting. From fe98988c8ddf823669ee516df188ca7625f38c0d Mon Sep 17 00:00:00 2001 From: Anthony Landreth Date: Fri, 3 Mar 2023 09:20:28 -0500 Subject: [PATCH 272/276] Bump go.mod This commit includes a minor fix for a log.Error call. Issue: [sc-17922] --- go.mod | 48 +++++++++++++++++++++++++--- go.sum | 11 ------- internal/operator/cluster/standby.go | 4 +-- 3 files changed, 45 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 2e48258e16..022226b37f 100644 --- a/go.mod +++ b/go.mod @@ -1,18 +1,15 @@ module github.com/crunchydata/postgres-operator -go 1.15 +go 1.19 require ( github.com/fatih/color v1.9.0 github.com/gorilla/mux v1.7.4 - github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect - github.com/mattn/go-colorable v0.1.6 // indirect github.com/nsqio/go-nsq v1.0.8 github.com/robfig/cron/v3 v3.0.1 github.com/sirupsen/logrus v1.5.0 github.com/spf13/cobra v0.0.5 github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.3.2 github.com/xdg/stringprep v1.0.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.13.0 go.opentelemetry.io/otel v0.13.0 @@ -20,10 +17,51 @@ require ( go.opentelemetry.io/otel/exporters/trace/jaeger v0.13.0 golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 - golang.org/x/tools v0.0.0-20210106214847-113979e3529a k8s.io/api v0.21.10 k8s.io/apimachinery v0.21.10 k8s.io/client-go v0.21.10 sigs.k8s.io/controller-runtime v0.6.4 sigs.k8s.io/yaml v1.2.0 ) + +require ( + github.com/DataDog/sketches-go v0.0.1 // indirect + github.com/apache/thrift v0.13.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/evanphx/json-patch v4.9.0+incompatible // indirect + github.com/felixge/httpsnoop v1.0.1 // indirect + github.com/go-logr/logr v0.4.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.0 // indirect + github.com/golang/snappy v0.0.1 // indirect + github.com/google/go-cmp v0.5.5 // indirect + github.com/google/gofuzz v1.1.0 // indirect + github.com/googleapis/gnostic v0.4.1 // indirect + github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/imdario/mergo v0.3.9 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/json-iterator/go v1.1.10 // indirect + github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect + github.com/mattn/go-colorable v0.1.6 // indirect + github.com/mattn/go-isatty v0.0.12 // indirect + github.com/moby/spdystream v0.2.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/pkg/errors v0.9.1 // indirect + go.opentelemetry.io/otel/sdk v0.13.0 // indirect + golang.org/x/net v0.0.0-20211209124913-491a49abca63 // indirect + golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 // indirect + golang.org/x/sys v0.0.0-20210426230700-d19ff857e887 // indirect + golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect + golang.org/x/text v0.3.6 // indirect + golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect + google.golang.org/api v0.32.0 // indirect + google.golang.org/appengine v1.6.6 // indirect + google.golang.org/protobuf v1.26.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + k8s.io/klog/v2 v2.9.0 // indirect + k8s.io/kube-openapi v0.0.0-20211110012726-3cc51fd1e909 // indirect + k8s.io/utils v0.0.0-20210521133846-da695404a2bc // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect +) diff --git a/go.sum b/go.sum index 982ddcbf42..4cf17ba4b9 100644 --- a/go.sum +++ b/go.sum @@ -46,7 +46,6 @@ github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6L github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/sketches-go v0.0.1 h1:RtG+76WKgZuz6FIaGsjoPePmadDBkuD/KC6+ZWu78b8= @@ -267,7 +266,6 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -300,7 +298,6 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -320,7 +317,6 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= @@ -352,7 +348,6 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= -github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -384,21 +379,17 @@ github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -667,7 +658,6 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a h1:CB3a9Nez8M13wwlr/E2YtwoU+qYHKfC+JrDa45RXXoQ= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -809,7 +799,6 @@ k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8 k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM= diff --git a/internal/operator/cluster/standby.go b/internal/operator/cluster/standby.go index ab6c5ef2ce..7a903e36a8 100644 --- a/internal/operator/cluster/standby.go +++ b/internal/operator/cluster/standby.go @@ -213,8 +213,8 @@ func EnableStandby(clientset kubernetes.Interface, cluster crv1.Pgcluster) error // Delete the "leader" configMap if err = clientset.CoreV1().ConfigMaps(namespace).Delete(ctx, leaderConfigMapName, metav1.DeleteOptions{}); err != nil && !kerrors.IsNotFound(err) { - log.Error("Unable to delete configMap %s while enabling standby mode for cluster "+ - "%s: %v", leaderConfigMapName, clusterName, err) + log.Error("Unable to delete configMap "+leaderConfigMapName+" while enabling standby mode for cluster "+ + leaderConfigMapName+":"+clusterName, err) return err } From b069edf7929976a3abbcd267518bff5aa1821f75 Mon Sep 17 00:00:00 2001 From: Anthony Landreth Date: Fri, 3 Mar 2023 16:12:16 -0500 Subject: [PATCH 273/276] fixes log.Error warning --- internal/operator/cluster/standby.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/operator/cluster/standby.go b/internal/operator/cluster/standby.go index 7a903e36a8..5c9bb6aa7f 100644 --- a/internal/operator/cluster/standby.go +++ b/internal/operator/cluster/standby.go @@ -213,8 +213,8 @@ func EnableStandby(clientset kubernetes.Interface, cluster crv1.Pgcluster) error // Delete the "leader" configMap if err = clientset.CoreV1().ConfigMaps(namespace).Delete(ctx, leaderConfigMapName, metav1.DeleteOptions{}); err != nil && !kerrors.IsNotFound(err) { - log.Error("Unable to delete configMap "+leaderConfigMapName+" while enabling standby mode for cluster "+ - leaderConfigMapName+":"+clusterName, err) + log.Errorf("Unable to delete configMap %s while enabling standby mode for cluster "+ + "%s: %v", leaderConfigMapName, clusterName, err) return err } From 927144fe8dc32ee147a9d1f9d0b84fdb3cc08b36 Mon Sep 17 00:00:00 2001 From: Anthony Landreth Date: Fri, 3 Mar 2023 16:13:06 -0500 Subject: [PATCH 274/276] removes unused OTel code --- cmd/postgres-operator/main.go | 21 +---- cmd/postgres-operator/open_telemetry.go | 101 ------------------------ 2 files changed, 1 insertion(+), 121 deletions(-) delete mode 100644 cmd/postgres-operator/open_telemetry.go diff --git a/cmd/postgres-operator/main.go b/cmd/postgres-operator/main.go index 8dda0c0520..3bfb05e485 100644 --- a/cmd/postgres-operator/main.go +++ b/cmd/postgres-operator/main.go @@ -39,12 +39,6 @@ import ( ) func main() { - if flush, err := initOpenTelemetry(); err != nil { - log.Fatal(err) - } else { - defer flush() - } - debugFlag := os.Getenv("CRUNCHY_DEBUG") // add logging configuration crunchylog.CrunchyLogger(crunchylog.SetParameters()) @@ -58,18 +52,7 @@ func main() { // give time for pgo-event to start up time.Sleep(time.Duration(5) * time.Second) - newKubernetesClient := func() (*kubeapi.Client, error) { - config, err := kubeapi.LoadClientConfig() - if err != nil { - return nil, err - } - - config.Wrap(otelTransportWrapper()) - - return kubeapi.NewClientForConfig(config) - } - - client, err := newKubernetesClient() + client, err := kubeapi.NewClient() if err != nil { log.Fatal(err) } @@ -96,8 +79,6 @@ func main() { } log.Debug("controller manager created") - controllerManager.NewKubernetesClient = newKubernetesClient - // If not using the "disabled" namespace operating mode, start a real namespace controller // that is able to resond to namespace events in the Kube cluster. If using the "disabled" // operating mode, then create a fake client containing all namespaces defined for the install diff --git a/cmd/postgres-operator/open_telemetry.go b/cmd/postgres-operator/open_telemetry.go deleted file mode 100644 index 7d5498bbf6..0000000000 --- a/cmd/postgres-operator/open_telemetry.go +++ /dev/null @@ -1,101 +0,0 @@ -package main - -/* -Copyright 2020 - 2023 Crunchy Data Solutions, Inc. -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. -*/ - -import ( - "fmt" - "io" - "net/http" - "os" - - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" - "go.opentelemetry.io/otel/api/global" - "go.opentelemetry.io/otel/exporters/stdout" - "go.opentelemetry.io/otel/exporters/trace/jaeger" -) - -func initOpenTelemetry() (func(), error) { - // At the time of this writing, the SDK (go.opentelemetry.io/otel@v0.13.0) - // does not automatically initialize any trace or metric exporter. An upcoming - // specification details environment variables that should facilitate this in - // the future. - // - // - https://github.com/open-telemetry/opentelemetry-specification/blob/f5519f2b/specification/sdk-environment-variables.md - - switch os.Getenv("OTEL_EXPORTER") { - case "jaeger": - var endpoint jaeger.EndpointOption - agent := os.Getenv("JAEGER_AGENT_ENDPOINT") - collector := jaeger.CollectorEndpointFromEnv() - - if agent != "" { - endpoint = jaeger.WithAgentEndpoint(agent) - } - if collector != "" { - endpoint = jaeger.WithCollectorEndpoint(collector) - } - - provider, flush, err := jaeger.NewExportPipeline(endpoint) - if err != nil { - return nil, fmt.Errorf("unable to initialize Jaeger exporter: %w", err) - } - - global.SetTracerProvider(provider) - return flush, nil - - case "json": - var closer io.Closer - filename := os.Getenv("OTEL_JSON_FILE") - options := []stdout.Option{stdout.WithoutMetricExport()} - - if filename != "" { - file, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) - if err != nil { - return nil, fmt.Errorf("unable to open exporter file: %w", err) - } - closer = file - options = append(options, stdout.WithWriter(file)) - } - - provider, pusher, err := stdout.NewExportPipeline(options, nil) - if err != nil { - return nil, fmt.Errorf("unable to initialize stdout exporter: %w", err) - } - flush := func() { - pusher.Stop() - if closer != nil { - _ = closer.Close() - } - } - - global.SetTracerProvider(provider) - return flush, nil - } - - // $OTEL_EXPORTER is unset or unknown, so no TracerProvider has been assigned. - // The default at this time is a single "no-op" tracer. - - return func() {}, nil -} - -// otelTransportWrapper creates a function that wraps the provided net/http.RoundTripper -// with one that starts a span for each request, injects context into that request, -// and ends the span when that request's response body is closed. -func otelTransportWrapper(options ...otelhttp.Option) func(http.RoundTripper) http.RoundTripper { - return func(rt http.RoundTripper) http.RoundTripper { - return otelhttp.NewTransport(rt, options...) - } -} From dc6b4ea28fae9d8d1ffca6bb240d7680034a6242 Mon Sep 17 00:00:00 2001 From: Anthony Landreth Date: Fri, 3 Mar 2023 16:13:38 -0500 Subject: [PATCH 275/276] upgrades specific dependencies --- go.mod | 19 +++++-------------- go.sum | 43 ++++++++++--------------------------------- 2 files changed, 15 insertions(+), 47 deletions(-) diff --git a/go.mod b/go.mod index 022226b37f..c2d5341893 100644 --- a/go.mod +++ b/go.mod @@ -11,11 +11,7 @@ require ( github.com/spf13/cobra v0.0.5 github.com/spf13/pflag v1.0.5 github.com/xdg/stringprep v1.0.0 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.13.0 - go.opentelemetry.io/otel v0.13.0 - go.opentelemetry.io/otel/exporters/stdout v0.13.0 - go.opentelemetry.io/otel/exporters/trace/jaeger v0.13.0 - golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 + golang.org/x/crypto v0.6.0 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 k8s.io/api v0.21.10 k8s.io/apimachinery v0.21.10 @@ -25,11 +21,8 @@ require ( ) require ( - github.com/DataDog/sketches-go v0.0.1 // indirect - github.com/apache/thrift v0.13.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/evanphx/json-patch v4.9.0+incompatible // indirect - github.com/felixge/httpsnoop v1.0.1 // indirect github.com/go-logr/logr v0.4.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.0 // indirect @@ -48,14 +41,12 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/pkg/errors v0.9.1 // indirect - go.opentelemetry.io/otel/sdk v0.13.0 // indirect - golang.org/x/net v0.0.0-20211209124913-491a49abca63 // indirect + golang.org/x/net v0.7.0 // indirect golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 // indirect - golang.org/x/sys v0.0.0-20210426230700-d19ff857e887 // indirect - golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect - golang.org/x/text v0.3.6 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/term v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect - google.golang.org/api v0.32.0 // indirect google.golang.org/appengine v1.6.6 // indirect google.golang.org/protobuf v1.26.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 4cf17ba4b9..6152c044e7 100644 --- a/go.sum +++ b/go.sum @@ -48,8 +48,6 @@ github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbt github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/sketches-go v0.0.1 h1:RtG+76WKgZuz6FIaGsjoPePmadDBkuD/KC6+ZWu78b8= -github.com/DataDog/sketches-go v0.0.1/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -60,13 +58,9 @@ github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4Rq github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/apache/thrift v0.13.0 h1:5hryIiq9gtn+MiLVn0wP37kb/uTeRZgN08WoCsAhIhI= -github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/benbjohnson/clock v1.0.3 h1:vkLuvpK4fmtSCuo60+yC63p7y0BmQ8gm5ZXGuBCJyXg= -github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= @@ -116,8 +110,6 @@ github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= -github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= @@ -225,7 +217,6 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -393,7 +384,6 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -423,18 +413,6 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opentelemetry.io/contrib v0.13.0 h1:q34CFu5REx9Dt2ksESHC/doIjFJkEg1oV3aSwlL5JR0= -go.opentelemetry.io/contrib v0.13.0/go.mod h1:HzCu6ebm0ywgNxGaEfs3izyJOMP4rZnzxycyTgpI5Sg= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.13.0 h1:dnZy1afzxEDrHybTYoJE1bQ3fphNwZF2ipSsynlITP4= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.13.0/go.mod h1:SeQm4RTCcZ2/hlMSTuHb7nwIROe5odBtgfKx+7MMqEs= -go.opentelemetry.io/otel v0.13.0 h1:2isEnyzjjJZq6r2EKMsFj4TxiQiexsM04AVhwbR/oBA= -go.opentelemetry.io/otel v0.13.0/go.mod h1:dlSNewoRYikTkotEnxdmuBHgzT+k/idJSfDv/FxEnOY= -go.opentelemetry.io/otel/exporters/stdout v0.13.0 h1:A+XiGIPQbGoJoBOJfKAKnZyiUSjSWvL3XWETUvtom5k= -go.opentelemetry.io/otel/exporters/stdout v0.13.0/go.mod h1:JJt8RpNY6K+ft9ir3iKpceCvT/rhzJXEExGrWFCbv1o= -go.opentelemetry.io/otel/exporters/trace/jaeger v0.13.0 h1:TjXcUVYbsjl3lYifrWptraZAL0OBmpMxRLm/eJ1GyZU= -go.opentelemetry.io/otel/exporters/trace/jaeger v0.13.0/go.mod h1:RSg6E40NYGqN/aCrStCUue2e+jABeFk2bKdNucw63ao= -go.opentelemetry.io/otel/sdk v0.13.0 h1:4VCfpKamZ8GtnepXxMRurSpHpMKkcxhtO33z1S4rGDQ= -go.opentelemetry.io/otel/sdk v0.13.0/go.mod h1:dKvLH8Uu8LcEPlSAUsfW7kMGaJBhk/1NYvpPZ6wIMbU= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= @@ -452,8 +430,9 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -522,8 +501,9 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrkoV51/tl5vOWqkcPGvY= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -585,24 +565,26 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210426230700-d19ff857e887 h1:dXfMednGJh/SUUFjTLsWJz3P+TQt9qnR11GgeI3vWKs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -657,7 +639,6 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -681,8 +662,6 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.32.0 h1:Le77IccnTqEa8ryp9wIpX5W3zYm7Gf9LhOp9PHcwFts= -google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -719,7 +698,6 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -733,7 +711,6 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From 951ba7314601be61b57f439e903fe91ab85fd478 Mon Sep 17 00:00:00 2001 From: Anthony Landreth Date: Fri, 3 Mar 2023 14:39:05 -0500 Subject: [PATCH 276/276] Prevent 4.6 make build target from breaking check Running make build breaks make check. Running make clean does not resolve the problem. The break comes from the license target pulling in Go files from packages installed on the system that are unrelated to the operator project. Now, make clean and make check will rm the licenses directory as needed. Issue: [sc-11938] --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 05e4557ee8..876512ef1d 100644 --- a/Makefile +++ b/Makefile @@ -200,6 +200,7 @@ pgo-base-docker: pgo-base-build #======== Utility ======= check: + rm -rf licenses/*/ PGOROOT=$(PGOROOT) go test ./... cli-docs: @@ -211,6 +212,7 @@ cli-docs: rm docs/content/pgo-client/reference/pgo.md clean: clean-deprecated + rm -rf licenses/*/ rm -f bin/apiserver rm -f bin/postgres-operator rm -f bin/pgo bin/pgo-mac bin/pgo.exe