From 5e1746e50a7d1648c7199150be65c118bfdb201d Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Wed, 30 Nov 2022 11:28:12 +0000 Subject: [PATCH 001/105] Add eksctl files for new AWS cluster --- eksctl/shared-hubs-cluster.jsonnet | 112 ++++++++++++++++++ .../ssh-keys/secret/shared-hubs-cluster.key | 21 ++++ eksctl/ssh-keys/shared-hubs-cluster.key.pub | 1 + 3 files changed, 134 insertions(+) create mode 100644 eksctl/shared-hubs-cluster.jsonnet create mode 100644 eksctl/ssh-keys/secret/shared-hubs-cluster.key create mode 100644 eksctl/ssh-keys/shared-hubs-cluster.key.pub diff --git a/eksctl/shared-hubs-cluster.jsonnet b/eksctl/shared-hubs-cluster.jsonnet new file mode 100644 index 0000000000..50e7245921 --- /dev/null +++ b/eksctl/shared-hubs-cluster.jsonnet @@ -0,0 +1,112 @@ +// Exports an eksctl config file for carbonplan cluster +local ng = import "./libsonnet/nodegroup.jsonnet"; + +// place all cluster nodes here +local clusterRegion = "us-west-2"; +local masterAzs = ["us-west-2a", "us-west-2b", "us-west-2c"]; +local nodeAz = "us-west-2a"; + +// Node definitions for notebook nodes. Config here is merged +// with our notebook node definition. +// A `node.kubernetes.io/instance-type label is added, so pods +// can request a particular kind of node with a nodeSelector +local notebookNodes = [ + { instanceType: "m5.large" }, + { instanceType: "m5.xlarge" }, + { instanceType: "m5.2xlarge" }, + { instanceType: "m5.8xlarge" }, +]; + +local daskNodes = + if "daskhub" == "daskhub" then [ + // Node definitions for dask worker nodes. Config here is merged + // with our dask worker node definition, which uses spot instances. + // A `node.kubernetes.io/instance-type label is set to the name of the + // *first* item in instanceDistribution.instanceTypes, to match + // what we do with notebook nodes. Pods can request a particular + // kind of node with a nodeSelector + { instancesDistribution+: { instanceTypes: ["m5.large"] }}, + { instancesDistribution+: { instanceTypes: ["m5.xlarge"] }}, + { instancesDistribution+: { instanceTypes: ["m5.2xlarge"] }}, + { instancesDistribution+: { instanceTypes: ["m5.8xlarge"] }}, + ]; +{ + apiVersion: 'eksctl.io/v1alpha5', + kind: 'ClusterConfig', + metadata+: { + name: "shared-hubs-cluster", + region: clusterRegion, + // Warning: version 1.23 introduces some breaking changes + // Checkout the docs before upgrading + // ref: https://docs.aws.amazon.com/eks/latest/userguide/ebs-csi-migration-faq.html + version: '1.22' + }, + availabilityZones: masterAzs, + iam: { + withOIDC: true, + }, + nodeGroups: [ + ng { + name: 'core-a', + availabilityZones: [nodeAz], + ssh: { + publicKeyPath: 'ssh-keys/shared-hubs-cluster.key.pub' + }, + instanceType: "m5.xlarge", + minSize: 1, + maxSize: 6, + labels+: { + "hub.jupyter.org/node-purpose": "core", + "k8s.dask.org/node-purpose": "core" + }, + }, + ] + [ + ng { + // NodeGroup names can't have a '.' in them, while + // instanceTypes always have a . + name: "nb-%s" % std.strReplace(n.instanceType, ".", "-"), + availabilityZones: [nodeAz], + minSize: 0, + maxSize: 500, + instanceType: n.instanceType, + ssh: { + publicKeyPath: 'ssh-keys/shared-hubs-cluster.key.pub' + }, + labels+: { + "hub.jupyter.org/node-purpose": "user", + "k8s.dask.org/node-purpose": "scheduler" + }, + taints+: { + "hub.jupyter.org_dedicated": "user:NoSchedule", + "hub.jupyter.org/dedicated": "user:NoSchedule" + }, + + } + n for n in notebookNodes + ] + ( if daskNodes != null then + [ + ng { + // NodeGroup names can't have a '.' in them, while + // instanceTypes always have a . + name: "dask-%s" % std.strReplace(n.instancesDistribution.instanceTypes[0], ".", "-"), + availabilityZones: [nodeAz], + minSize: 0, + maxSize: 500, + ssh: { + publicKeyPath: 'ssh-keys/shared-hubs-cluster.key.pub' + }, + labels+: { + "k8s.dask.org/node-purpose": "worker" + }, + taints+: { + "k8s.dask.org_dedicated" : "worker:NoSchedule", + "k8s.dask.org/dedicated" : "worker:NoSchedule" + }, + instancesDistribution+: { + onDemandBaseCapacity: 0, + onDemandPercentageAboveBaseCapacity: 0, + spotAllocationStrategy: "capacity-optimized", + }, + } + n for n in daskNodes + ] else [] + ) +} \ No newline at end of file diff --git a/eksctl/ssh-keys/secret/shared-hubs-cluster.key b/eksctl/ssh-keys/secret/shared-hubs-cluster.key new file mode 100644 index 0000000000..1953485034 --- /dev/null +++ b/eksctl/ssh-keys/secret/shared-hubs-cluster.key @@ -0,0 +1,21 @@ +{ + "data": "ENC[AES256_GCM,data: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,iv:GpFd3mgEG/XFUIKaqrQDeGK7BINl0f8hkeYKBhZTrsI=,tag:6JiZVvnatz+E4zw99DDQmg==,type:str]", + "sops": { + "kms": null, + "gcp_kms": [ + { + "resource_id": "projects/two-eye-two-see/locations/global/keyRings/sops-keys/cryptoKeys/similar-hubs", + "created_at": "2022-11-30T11:19:43Z", + "enc": "CiUA4OM7eCzZe7vZVl3dWQ/xGiXsJanYBfOikD3K50a8O11Q/NzXEkgA+0T9hdnqI92imnVspFdEh9IBkh7LZHZjmzalHG9CU0Wh/saAr25QpzEQaAQhyaubdGENj53KuGW2w75sPU3bUhdgSS0nCZo=" + } + ], + "azure_kv": null, + "hc_vault": null, + "age": null, + "lastmodified": "2022-11-30T11:19:43Z", + "mac": "ENC[AES256_GCM,data:WTiGyCMo5Jnh1jog/JVjMpUsU58KNns+JkQ7igxIQ1nk39c/PVz8cws0ROpw3QAaDvrRGuYnfhndXEIVTW1wXe4Qs4LtgwsBsD2GhmWaAyjEmnxDaiQ6bUxhU7cPz+3lPYH6Fr1Ipk9EzimmOGoKS1/2eMOkKjBlvZ/OLYpYEAg=,iv:rPZrIfkjGEV7bVl56Zb1cSic73RWC7SFMFrp2caGQd4=,tag:A+wl8o9eVia495krt1XnIg==,type:str]", + "pgp": null, + "unencrypted_suffix": "_unencrypted", + "version": "3.7.3" + } +} \ No newline at end of file diff --git a/eksctl/ssh-keys/shared-hubs-cluster.key.pub b/eksctl/ssh-keys/shared-hubs-cluster.key.pub new file mode 100644 index 0000000000..8124d851de --- /dev/null +++ b/eksctl/ssh-keys/shared-hubs-cluster.key.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC2GP+rT4i3Hed1cWky3F6M1Xnp+hQ3w10RDiC1qUtFu2gGXliHhoxKDpuxF0OpTgK5M3ZwS52qVKajXGwVmdRohVas1njYdJbECxdGHBj7EKYPdNmsxhdjd2IVipMRIdwOJ6ckSKUe3ypRjLhuB1IuxooI5NNAKkBI73d46yObTkcwNhxxdI+Sj+jBtaGyAzzuvg1vJiIMsegCQzJREHmOElJQLpD384O27ytXdL17ldzcQ92ydM01omWhKDP8e/j0BGxjJ+r69jxrqqYHI8P/UxIjhJqnHH6JGSJeBCQfN06dalWLaVPOGCiKfouhWAmEqFfftvkXDfMIkrc6iCOX7I7Zm070iArn+rm4CPvCFMMjXhp9fVCmScsIv6OY0WBbuJMByY0Qun0zET1/cSNq7g2vFVvQJyTJpJlPFlFA5p792etASPHH8EAU9N5VEbjm5Y6XDAfSxljP+SMNZlVRov6hpV7PFolLxeiNJROJhykcKbUnZCD0GbQadb6OKRc= sgibson@Athena.broadband From 4e1031d74b587d09ffcccdd4ccfabba65f2ddbd4 Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Wed, 30 Nov 2022 11:51:50 +0000 Subject: [PATCH 002/105] Add tfvars file for new AWS cluster --- .../aws/projects/shared-hubs-cluster.tfvars | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 terraform/aws/projects/shared-hubs-cluster.tfvars diff --git a/terraform/aws/projects/shared-hubs-cluster.tfvars b/terraform/aws/projects/shared-hubs-cluster.tfvars new file mode 100644 index 0000000000..05a111d934 --- /dev/null +++ b/terraform/aws/projects/shared-hubs-cluster.tfvars @@ -0,0 +1,28 @@ +region = "us-west-2" + +cluster_name = "shared-hubs-cluster" + +cluster_nodes_location = "us-west-2a" + +user_buckets = { + "scratch-staging": { + "delete_after" : 7 + }, + "scratch": { + "delete_after": 7 + }, +} + + +hub_cloud_permissions = { + "staging" : { + requestor_pays: true, + bucket_admin_access: ["scratch-staging"], + extra_iam_policy: "" + }, + "prod" : { + requestor_pays: true, + bucket_admin_access: ["scratch"], + extra_iam_policy: "" + }, +} \ No newline at end of file From 0092c70c09a94702fd7b89c2cb2d4793094619dd Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Fri, 2 Dec 2022 14:47:35 +0000 Subject: [PATCH 003/105] Rename cluster shared-hubs-cluster was too broad, 2i2c-aws-us is more specific --- ...bs-cluster.jsonnet => 2i2c-aws-us.jsonnet} | 8 +++---- eksctl/ssh-keys/2i2c-aws-us.key.pub | 1 + eksctl/ssh-keys/secret/2i2c-aws-us.key | 21 +++++++++++++++++++ .../ssh-keys/secret/shared-hubs-cluster.key | 21 ------------------- eksctl/ssh-keys/shared-hubs-cluster.key.pub | 1 - ...hubs-cluster.tfvars => 2i2c-aws-us.tfvars} | 2 +- 6 files changed, 27 insertions(+), 27 deletions(-) rename eksctl/{shared-hubs-cluster.jsonnet => 2i2c-aws-us.jsonnet} (93%) create mode 100644 eksctl/ssh-keys/2i2c-aws-us.key.pub create mode 100644 eksctl/ssh-keys/secret/2i2c-aws-us.key delete mode 100644 eksctl/ssh-keys/secret/shared-hubs-cluster.key delete mode 100644 eksctl/ssh-keys/shared-hubs-cluster.key.pub rename terraform/aws/projects/{shared-hubs-cluster.tfvars => 2i2c-aws-us.tfvars} (92%) diff --git a/eksctl/shared-hubs-cluster.jsonnet b/eksctl/2i2c-aws-us.jsonnet similarity index 93% rename from eksctl/shared-hubs-cluster.jsonnet rename to eksctl/2i2c-aws-us.jsonnet index 50e7245921..71ce93ee85 100644 --- a/eksctl/shared-hubs-cluster.jsonnet +++ b/eksctl/2i2c-aws-us.jsonnet @@ -34,7 +34,7 @@ local daskNodes = apiVersion: 'eksctl.io/v1alpha5', kind: 'ClusterConfig', metadata+: { - name: "shared-hubs-cluster", + name: "2i2c-aws-us", region: clusterRegion, // Warning: version 1.23 introduces some breaking changes // Checkout the docs before upgrading @@ -50,7 +50,7 @@ local daskNodes = name: 'core-a', availabilityZones: [nodeAz], ssh: { - publicKeyPath: 'ssh-keys/shared-hubs-cluster.key.pub' + publicKeyPath: 'ssh-keys/2i2c-aws-us.key.pub' }, instanceType: "m5.xlarge", minSize: 1, @@ -70,7 +70,7 @@ local daskNodes = maxSize: 500, instanceType: n.instanceType, ssh: { - publicKeyPath: 'ssh-keys/shared-hubs-cluster.key.pub' + publicKeyPath: 'ssh-keys/2i2c-aws-us.key.pub' }, labels+: { "hub.jupyter.org/node-purpose": "user", @@ -92,7 +92,7 @@ local daskNodes = minSize: 0, maxSize: 500, ssh: { - publicKeyPath: 'ssh-keys/shared-hubs-cluster.key.pub' + publicKeyPath: 'ssh-keys/2i2c-aws-us.key.pub' }, labels+: { "k8s.dask.org/node-purpose": "worker" diff --git a/eksctl/ssh-keys/2i2c-aws-us.key.pub b/eksctl/ssh-keys/2i2c-aws-us.key.pub new file mode 100644 index 0000000000..b3db6db682 --- /dev/null +++ b/eksctl/ssh-keys/2i2c-aws-us.key.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDCgMC4QWQZ6tCOy3XndxSU5dCt0Tf/b8IiqilsQYftJ3MD/174DPhTamG8RtCUZp7i0C66jw7vhaBM+fvP3q6njzIU7xM4EebPVN/Kry4H0dq+qFTyDEZ5p+nMTEy6KKbbcQyPJwVZN88R0DgykIx083UYxSVrdcBwsXolfbSZSju4Vih5RPB0mud41coF6NlSw+swtO0KTpyl4tFsE+zoyp+S8lTdOaSptl9bJEP5xHXRI4dvVbwFW7UBgM6TDlvKHG9CUj2b+T58CRRc1UQAXueSeq9a//Ndf6IWU/HsDVdBD1xT5QPgAcjpmYSvjJFWCLhbhTIXJnGPxOmJqBkJG0rsEHP8bnjQbGvizbAUDfBqwyVShIaEhW8rQt4YMmeI46sG+zFhTW2dlHeIBrFUS3L8ZaRTRkYzx9CLs9hiuJq1mODutgzUxhJ9tXQTLDjKOKGodIcEAy3LGVxsaDQiqN9+R2WpgmNybju6NOoeiumuEI3rCaI0rXqXmaEc/x0= sgibson@Athena.broadband diff --git a/eksctl/ssh-keys/secret/2i2c-aws-us.key b/eksctl/ssh-keys/secret/2i2c-aws-us.key new file mode 100644 index 0000000000..a8e41d4924 --- /dev/null +++ b/eksctl/ssh-keys/secret/2i2c-aws-us.key @@ -0,0 +1,21 @@ +{ + "data": "ENC[AES256_GCM,data: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,iv:Fe0Oi4dLsqSE/tdEWtk2nmSTJvaB03v7SG0+IsM2K2Y=,tag:BxYNuLutpt5ql/0HcSToEw==,type:str]", + "sops": { + "kms": null, + "gcp_kms": [ + { + "resource_id": "projects/two-eye-two-see/locations/global/keyRings/sops-keys/cryptoKeys/similar-hubs", + "created_at": "2022-12-02T14:38:35Z", + "enc": "CiUA4OM7eET6m+9um6tWo2fF4m/2G2LzwquO9zw++n6FYZdp6jQPEkkA+0T9hZyqmQo2/iKlxfETQFnkBXfLIPIS0ev/XWIKXNRmuyIxrNKO/BnfvoBWPODSYTm1SMAdGOJdv2/FdyaTMSDQa5Og/qzb" + } + ], + "azure_kv": null, + "hc_vault": null, + "age": null, + "lastmodified": "2022-12-02T14:38:35Z", + "mac": "ENC[AES256_GCM,data:KhMAcv1+wxjAOsQ1zcC92iLCnrHsQr9UVI3lVmVmu6bRMKG6aqqfPKBXqitUXOr/P6YvCx+IJ00GNXICcNjaruzGg72xno585d4hmR2NWNt70Y53bQXG6zsRbF1tepXoSm69Hwgx3ek3YXJSmJMcN6LLLAwKbfaTw42hplF4Cn0=,iv:q1epqz2kis8XoJ9dNYrofEYiyK71lDl8Max07RmEGps=,tag:B+27n5aaLHhQJFRr/62f/A==,type:str]", + "pgp": null, + "unencrypted_suffix": "_unencrypted", + "version": "3.7.3" + } +} \ No newline at end of file diff --git a/eksctl/ssh-keys/secret/shared-hubs-cluster.key b/eksctl/ssh-keys/secret/shared-hubs-cluster.key deleted file mode 100644 index 1953485034..0000000000 --- a/eksctl/ssh-keys/secret/shared-hubs-cluster.key +++ /dev/null @@ -1,21 +0,0 @@ -{ - "data": "ENC[AES256_GCM,data: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,iv:GpFd3mgEG/XFUIKaqrQDeGK7BINl0f8hkeYKBhZTrsI=,tag:6JiZVvnatz+E4zw99DDQmg==,type:str]", - "sops": { - "kms": null, - "gcp_kms": [ - { - "resource_id": "projects/two-eye-two-see/locations/global/keyRings/sops-keys/cryptoKeys/similar-hubs", - "created_at": "2022-11-30T11:19:43Z", - "enc": "CiUA4OM7eCzZe7vZVl3dWQ/xGiXsJanYBfOikD3K50a8O11Q/NzXEkgA+0T9hdnqI92imnVspFdEh9IBkh7LZHZjmzalHG9CU0Wh/saAr25QpzEQaAQhyaubdGENj53KuGW2w75sPU3bUhdgSS0nCZo=" - } - ], - "azure_kv": null, - "hc_vault": null, - "age": null, - "lastmodified": "2022-11-30T11:19:43Z", - "mac": "ENC[AES256_GCM,data:WTiGyCMo5Jnh1jog/JVjMpUsU58KNns+JkQ7igxIQ1nk39c/PVz8cws0ROpw3QAaDvrRGuYnfhndXEIVTW1wXe4Qs4LtgwsBsD2GhmWaAyjEmnxDaiQ6bUxhU7cPz+3lPYH6Fr1Ipk9EzimmOGoKS1/2eMOkKjBlvZ/OLYpYEAg=,iv:rPZrIfkjGEV7bVl56Zb1cSic73RWC7SFMFrp2caGQd4=,tag:A+wl8o9eVia495krt1XnIg==,type:str]", - "pgp": null, - "unencrypted_suffix": "_unencrypted", - "version": "3.7.3" - } -} \ No newline at end of file diff --git a/eksctl/ssh-keys/shared-hubs-cluster.key.pub b/eksctl/ssh-keys/shared-hubs-cluster.key.pub deleted file mode 100644 index 8124d851de..0000000000 --- a/eksctl/ssh-keys/shared-hubs-cluster.key.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC2GP+rT4i3Hed1cWky3F6M1Xnp+hQ3w10RDiC1qUtFu2gGXliHhoxKDpuxF0OpTgK5M3ZwS52qVKajXGwVmdRohVas1njYdJbECxdGHBj7EKYPdNmsxhdjd2IVipMRIdwOJ6ckSKUe3ypRjLhuB1IuxooI5NNAKkBI73d46yObTkcwNhxxdI+Sj+jBtaGyAzzuvg1vJiIMsegCQzJREHmOElJQLpD384O27ytXdL17ldzcQ92ydM01omWhKDP8e/j0BGxjJ+r69jxrqqYHI8P/UxIjhJqnHH6JGSJeBCQfN06dalWLaVPOGCiKfouhWAmEqFfftvkXDfMIkrc6iCOX7I7Zm070iArn+rm4CPvCFMMjXhp9fVCmScsIv6OY0WBbuJMByY0Qun0zET1/cSNq7g2vFVvQJyTJpJlPFlFA5p792etASPHH8EAU9N5VEbjm5Y6XDAfSxljP+SMNZlVRov6hpV7PFolLxeiNJROJhykcKbUnZCD0GbQadb6OKRc= sgibson@Athena.broadband diff --git a/terraform/aws/projects/shared-hubs-cluster.tfvars b/terraform/aws/projects/2i2c-aws-us.tfvars similarity index 92% rename from terraform/aws/projects/shared-hubs-cluster.tfvars rename to terraform/aws/projects/2i2c-aws-us.tfvars index 05a111d934..8e929264db 100644 --- a/terraform/aws/projects/shared-hubs-cluster.tfvars +++ b/terraform/aws/projects/2i2c-aws-us.tfvars @@ -1,6 +1,6 @@ region = "us-west-2" -cluster_name = "shared-hubs-cluster" +cluster_name = "2i2c-aws-us" cluster_nodes_location = "us-west-2a" From cded1f113fee41f0ed5fe19fb44a1c638d5b7e52 Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Fri, 2 Dec 2022 15:37:31 +0000 Subject: [PATCH 004/105] Update tfvars file with new namespaces --- terraform/aws/projects/2i2c-aws-us.tfvars | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/terraform/aws/projects/2i2c-aws-us.tfvars b/terraform/aws/projects/2i2c-aws-us.tfvars index 8e929264db..1b2cd62613 100644 --- a/terraform/aws/projects/2i2c-aws-us.tfvars +++ b/terraform/aws/projects/2i2c-aws-us.tfvars @@ -8,7 +8,10 @@ user_buckets = { "scratch-staging": { "delete_after" : 7 }, - "scratch": { + "scratch-dask-staging": { + "delete_after": 7 + }, + "scratch-research-delight": { "delete_after": 7 }, } @@ -20,9 +23,14 @@ hub_cloud_permissions = { bucket_admin_access: ["scratch-staging"], extra_iam_policy: "" }, - "prod" : { + "dask-staging" : { + requestor_pays: true, + bucket_admin_access: ["scratch-dask-staging"], + extra_iam_policy: "" + }, + "research-delight" : { requestor_pays: true, - bucket_admin_access: ["scratch"], + bucket_admin_access: ["scratch-research-delight"], extra_iam_policy: "" }, -} \ No newline at end of file +} From e8c8163c2458151633890cf3f083fa18c72d8a57 Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Fri, 2 Dec 2022 16:32:53 +0000 Subject: [PATCH 005/105] Add deployer credentials --- .../enc-deployer-credentials.secret.json | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 config/clusters/2i2c-aws-us/enc-deployer-credentials.secret.json diff --git a/config/clusters/2i2c-aws-us/enc-deployer-credentials.secret.json b/config/clusters/2i2c-aws-us/enc-deployer-credentials.secret.json new file mode 100644 index 0000000000..537b95654c --- /dev/null +++ b/config/clusters/2i2c-aws-us/enc-deployer-credentials.secret.json @@ -0,0 +1,25 @@ +{ + "AccessKey": { + "AccessKeyId": "ENC[AES256_GCM,data:6PvELWFuIRU3X/xw0KFAUB9tALw=,iv:qAUuL9XpmqVfmIPAg1jVLw6BOgOLdI6Nh9ciGoYR1ko=,tag:YkB81KaQkneoErdvbA+gcQ==,type:str]", + "SecretAccessKey": "ENC[AES256_GCM,data:yL9fFhLZwIwYGIcH0rMlom1AMf8SP8rHuqIaFcCMHgxJrvBwrIjEtA==,iv:QUL+KmQVs+fqwei8qATePtz7DYRGISeqK76C3C6ZYHQ=,tag:1JKsN+6MwzrwncgcnJOY0Q==,type:str]", + "UserName": "ENC[AES256_GCM,data:uINElYnC8kT9jENDB0HZobYeJRtg0pQ=,iv:6WcmGN4Uo/0ESepK+6GnNJzqhOiKN7XUlyCmDBV9wbs=,tag:rtoBQVLgs4iwHF9xxWY5LA==,type:str]" + }, + "sops": { + "kms": null, + "gcp_kms": [ + { + "resource_id": "projects/two-eye-two-see/locations/global/keyRings/sops-keys/cryptoKeys/similar-hubs", + "created_at": "2022-12-02T16:26:52Z", + "enc": "CiUA4OM7eOLLa/imL4GpHRd9gcsB84MVB/Ad4qDdcZR1yN5dCQEEEkkA+0T9hZ4MsWpzgSLLO687tPm2nrUQ+Ah6/7KRQH66x5sYPrKozkBm5ch5T2Y8YTXSb2stXzIlTqQA9Eq8sBc7rTyEG0G+Ryad" + } + ], + "azure_kv": null, + "hc_vault": null, + "age": null, + "lastmodified": "2022-12-02T16:26:52Z", + "mac": "ENC[AES256_GCM,data:csHlSvetk7n+RSN1OK5ZbFND3bRd/liGLiStaY0XsXTXjzq2okXjazEKeS6tNIRt6MKsLNQ3LOv84q7nmHnbLx2/jlBhmKu9oRwd5CBdponB/14CRnPhpI/cVm9i6D/FTUi/Wy5MVojJZdj/4DIMyN5cjpWeQSYAATuFzbsYPKU=,iv:JZAhUHxxurCQRStaMCoL6qu5JihtVOrnFpXFWQDx8Xk=,tag:azOozPUyO9iz0SnVfbt+Sw==,type:str]", + "pgp": null, + "unencrypted_suffix": "_unencrypted", + "version": "3.7.3" + } +} \ No newline at end of file From 3a035bfe97bf48588bcb0de7deac5a8529f616c7 Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Fri, 2 Dec 2022 16:33:03 +0000 Subject: [PATCH 006/105] Create minimal cluster.yaml file --- config/clusters/2i2c-aws-us/cluster.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 config/clusters/2i2c-aws-us/cluster.yaml diff --git a/config/clusters/2i2c-aws-us/cluster.yaml b/config/clusters/2i2c-aws-us/cluster.yaml new file mode 100644 index 0000000000..174870406c --- /dev/null +++ b/config/clusters/2i2c-aws-us/cluster.yaml @@ -0,0 +1,9 @@ +name: 2i2c-aws-us +provider: aws +aws: + key: enc-deployer-credentials.secret.json + clusterType: eks + clusterName: 2i2c-aws-us + region: us-west-2 +support: [] +hubs: [] From d163ed0cfec509e6a9b21dce2481debc4268a572 Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Fri, 2 Dec 2022 16:40:50 +0000 Subject: [PATCH 007/105] Add new cluster to CI/CD workflows --- .github/workflows/deploy-grafana-dashboards.yaml | 1 + .github/workflows/deploy-hubs.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/deploy-grafana-dashboards.yaml b/.github/workflows/deploy-grafana-dashboards.yaml index cf7febe504..08b198b9e0 100644 --- a/.github/workflows/deploy-grafana-dashboards.yaml +++ b/.github/workflows/deploy-grafana-dashboards.yaml @@ -27,6 +27,7 @@ jobs: - cluster_name: nasa-cryo - cluster_name: gridsst - cluster_name: victor + - cluster-name: 2i2c-aws-us steps: - name: Checkout repo uses: actions/checkout@v3 diff --git a/.github/workflows/deploy-hubs.yaml b/.github/workflows/deploy-hubs.yaml index c58dd353a0..83b4856547 100644 --- a/.github/workflows/deploy-hubs.yaml +++ b/.github/workflows/deploy-hubs.yaml @@ -162,6 +162,7 @@ jobs: failure_nasa-cryo: "${{ env.failure_nasa-cryo }}" failure_gridsst: "${{ env.failure_gridsst }}" failure_victor: "${{ env.failure_victor }}" + failure_2i2c-aws-us: "${{ env.failure_2i2c-aws-us }}" # Only run this job on pushes to the default branch and when the job output is not # an empty list From 55498900e77ea209aa9e88e3ba86be2014d8782c Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Fri, 2 Dec 2022 17:01:44 +0000 Subject: [PATCH 008/105] Add support chart values files --- config/clusters/2i2c-aws-us/cluster.yaml | 5 ++- .../enc-support.secret.values.yaml | 17 ++++++++++ .../clusters/2i2c-aws-us/support.values.yaml | 31 +++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 config/clusters/2i2c-aws-us/enc-support.secret.values.yaml create mode 100644 config/clusters/2i2c-aws-us/support.values.yaml diff --git a/config/clusters/2i2c-aws-us/cluster.yaml b/config/clusters/2i2c-aws-us/cluster.yaml index 174870406c..8fc50c057b 100644 --- a/config/clusters/2i2c-aws-us/cluster.yaml +++ b/config/clusters/2i2c-aws-us/cluster.yaml @@ -5,5 +5,8 @@ aws: clusterType: eks clusterName: 2i2c-aws-us region: us-west-2 -support: [] +support: + helm_chart_values_files: + - support.values.yaml + - enc-support.secret.values.yaml hubs: [] diff --git a/config/clusters/2i2c-aws-us/enc-support.secret.values.yaml b/config/clusters/2i2c-aws-us/enc-support.secret.values.yaml new file mode 100644 index 0000000000..1919d26980 --- /dev/null +++ b/config/clusters/2i2c-aws-us/enc-support.secret.values.yaml @@ -0,0 +1,17 @@ +prometheusIngressAuthSecret: + username: ENC[AES256_GCM,data:4DPd7lTWaJ8ND8GceLh6wNzHAY1LeqMIokEHBqObThtXtnfpmE11z7FIMb1Vant+di7SbbCAPcoGgUdvUOsXLQ==,iv:kj0DnH2cCzw7iBraX/dfjENXOIgk/ZC/qCjseisno/c=,tag:AgGZJfGRBsqaYlzqpvSpbg==,type:str] + password: ENC[AES256_GCM,data:Yt6DejIrACYstASWXxBPshCm6/n4vsSw7NK1RxH9fKMxjn+Bs1x6+MKD7Im2IRxWzSoP3XtURJjmaYdio98U/Q==,iv:HGnhJhRX7O/nn4y4HujGT18nJoB3kPMD0x+x/PSbjps=,tag:hCnTt0eWn57EH/3k5HXexQ==,type:str] +sops: + kms: [] + gcp_kms: + - resource_id: projects/two-eye-two-see/locations/global/keyRings/sops-keys/cryptoKeys/similar-hubs + created_at: "2022-12-02T16:48:54Z" + enc: CiUA4OM7eJkeDQA+Fs2q6h7jkeWzZ2OWVQlePA+DEhpqTrCjE2EQEkkA+0T9hez3llYUQUfMYkGtA1OlIM9nfVFocn4ceORhXKFmBGIsVBP352iqjf+YFQMLHdLh2LJx4PdrpvkgNhW/M5dGa+41P1px + azure_kv: [] + hc_vault: [] + age: [] + lastmodified: "2022-12-02T16:48:55Z" + mac: ENC[AES256_GCM,data:0jbzDZ/5aY022XU6u3ftcafKdnujPZNOowH4zyafVDSkDAZJAKQE4GnJpW9s32NVSkptmnCA6Xr2u0dm7UAuWBFrepnHt0PLTJaUr33DtgAO8QfwXbDAD2MdrAPoH1Lvd43XwTqCY4ujX8Of/Vd6FsfIToHdKrDv3znI+6g6UTs=,iv:56pLW3wQ7AenXVl9GaI/A/e09co9F2gLdVznX57w66E=,tag:QWr3kQuOuGRaCWXIYE7Xtg==,type:str] + pgp: [] + unencrypted_suffix: _unencrypted + version: 3.7.3 diff --git a/config/clusters/2i2c-aws-us/support.values.yaml b/config/clusters/2i2c-aws-us/support.values.yaml new file mode 100644 index 0000000000..823d037f24 --- /dev/null +++ b/config/clusters/2i2c-aws-us/support.values.yaml @@ -0,0 +1,31 @@ +prometheusIngressAuthSecret: + enabled: true + +grafana: + grafana.ini: + server: + root_url: https://grafana.aws.2i2c.cloud/ + ingress: + hosts: + - grafana.aws.2i2c.cloud + tls: + - secretName: grafana-tls + hosts: + - grafana.aws.2i2c.cloud + +prometheus: + server: + ingress: + enabled: true + hosts: + - prometheus.aws.2i2c.cloud + tls: + - secretName: prometheus-tls + hosts: + - prometheus.aws.2i2c.cloud + +cluster-autoscaler: + enabled: true + autoDiscovery: + clusterName: 2i2c-aws-us + awsRegion: us-west-2 From afab15e4946a4c392a1291df6a894e27111482f7 Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Fri, 2 Dec 2022 17:07:23 +0000 Subject: [PATCH 009/105] Add a grafana API token for deploying dashboards --- .../2i2c-aws-us/enc-grafana-token.secret.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 config/clusters/2i2c-aws-us/enc-grafana-token.secret.yaml diff --git a/config/clusters/2i2c-aws-us/enc-grafana-token.secret.yaml b/config/clusters/2i2c-aws-us/enc-grafana-token.secret.yaml new file mode 100644 index 0000000000..e8ed2eaf5a --- /dev/null +++ b/config/clusters/2i2c-aws-us/enc-grafana-token.secret.yaml @@ -0,0 +1,15 @@ +grafana_token: ENC[AES256_GCM,data:t2qk7Ze8CPLm04pnwFDpqhoXv430t1wBH+o4H3KxGLLPZXeeBfMgHNTjOFeX0xABJ8eZw8yRi/TRXHfil9QQ+OKoTIMJ9olTr++YdBEVBRqQijlFJTc5IVRQQXNR9LhG,iv:9vVNYHRyCOexYjx49JMHqxD8RbYSs4OaU2RsDFjW1N4=,tag:Wgnr/0UIGJiCuQPULCfgbA==,type:str] +sops: + kms: [] + gcp_kms: + - resource_id: projects/two-eye-two-see/locations/global/keyRings/sops-keys/cryptoKeys/similar-hubs + created_at: "2022-12-02T17:06:57Z" + enc: CiUA4OM7eE9zAnsvqqZ1DkU29yZuDSdm7AoElAMABbgu9/p8tLGkEkkA+0T9hXy+VFXSo6A4H8d8HFNwQBsm67tqAGBBUQ7SlIoWIz28wMPTIez5sNPBziRv9VrsXuIFJozC+Z2oqwexcy6Wy6663t9I + azure_kv: [] + hc_vault: [] + age: [] + lastmodified: "2022-12-02T17:06:58Z" + mac: ENC[AES256_GCM,data:NHmFKLI8yUkZW+lhh9+rZl0LhTjBFbVgAua1M4E2+5DcDY+tA1tO87T1QcPcX+PPAPIXTO41eofFB01MbfG1FbsXjPYQE3235pmA2WNEIaeY2fTAdKylrUfbiICV/2doex/eXLdQwIrXJeyyrIYXA0Sjj1hDBkuV6DCU8HbKQ+s=,iv:1XrSnJHntEpQ9qx0MbOEfHpzpeFiIIoK0eCsEQCVI40=,tag:4+9qqk4LXBriUpPMSbTlew==,type:str] + pgp: [] + unencrypted_suffix: _unencrypted + version: 3.7.3 From e281e758c6361f09ea1ef14696b910271e314875 Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Fri, 2 Dec 2022 17:51:19 +0000 Subject: [PATCH 010/105] Add config for a staging hub --- config/clusters/2i2c-aws-us/cluster.yaml | 11 ++++++- .../clusters/2i2c-aws-us/staging.values.yaml | 32 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 config/clusters/2i2c-aws-us/staging.values.yaml diff --git a/config/clusters/2i2c-aws-us/cluster.yaml b/config/clusters/2i2c-aws-us/cluster.yaml index 8fc50c057b..5ed0748047 100644 --- a/config/clusters/2i2c-aws-us/cluster.yaml +++ b/config/clusters/2i2c-aws-us/cluster.yaml @@ -9,4 +9,13 @@ support: helm_chart_values_files: - support.values.yaml - enc-support.secret.values.yaml -hubs: [] +hubs: + - name: staging + display_name: "2i2c AWS staging" + domain: staging.aws.2i2c.cloud + helm_chart: basehub + auth0: + connection: google-oauth2 + helm_chart_values_files: + - staging.values.yaml + diff --git a/config/clusters/2i2c-aws-us/staging.values.yaml b/config/clusters/2i2c-aws-us/staging.values.yaml new file mode 100644 index 0000000000..e382a035a2 --- /dev/null +++ b/config/clusters/2i2c-aws-us/staging.values.yaml @@ -0,0 +1,32 @@ +nfs: + pv: + # from https://docs.aws.amazon.com/efs/latest/ug/mounting-fs-nfs-mount-settings.html + mountOptions: + - rsize=1048576 + - wsize=1048576 + - timeo=600 + - soft # We pick soft over hard, so NFS lockups don't lead to hung processes + - retrans=2 + - noresvport + serverIP: fs-0b70db2b65209a77d.efs.us-west-2.amazonaws.com + baseShareName: / +jupyterhub: + custom: + 2i2c: + add_staff_user_ids_to_admin_users: true + add_staff_user_ids_of_type: "google" + homepage: + templateVars: + org: + name: 2i2c AWS Staging + url: https://2i2c.org + logo_url: https://2i2c.org/media/logo.png + designed_by: + name: 2i2c + url: https://2i2c.org + operated_by: + name: 2i2c + url: https://2i2c.org + funded_by: + name: 2i2c + url: https://2i2c.org From 92e8f0985066fb930c78e7535579ccb6727b2cad Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 2 Dec 2022 17:51:38 +0000 Subject: [PATCH 011/105] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- config/clusters/2i2c-aws-us/cluster.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/config/clusters/2i2c-aws-us/cluster.yaml b/config/clusters/2i2c-aws-us/cluster.yaml index 5ed0748047..c9eeb9a3ad 100644 --- a/config/clusters/2i2c-aws-us/cluster.yaml +++ b/config/clusters/2i2c-aws-us/cluster.yaml @@ -18,4 +18,3 @@ hubs: connection: google-oauth2 helm_chart_values_files: - staging.values.yaml - From 0363dc8b7f82495550b3b9ea02fffcd2ff98a9d8 Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Fri, 2 Dec 2022 18:06:18 +0000 Subject: [PATCH 012/105] rename buckets hyphen is not in the hub image name James has created, so let's be consistent --- terraform/aws/projects/2i2c-aws-us.tfvars | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/terraform/aws/projects/2i2c-aws-us.tfvars b/terraform/aws/projects/2i2c-aws-us.tfvars index 1b2cd62613..c0e82b4902 100644 --- a/terraform/aws/projects/2i2c-aws-us.tfvars +++ b/terraform/aws/projects/2i2c-aws-us.tfvars @@ -11,7 +11,7 @@ user_buckets = { "scratch-dask-staging": { "delete_after": 7 }, - "scratch-research-delight": { + "scratch-researchdelight": { "delete_after": 7 }, } @@ -28,9 +28,9 @@ hub_cloud_permissions = { bucket_admin_access: ["scratch-dask-staging"], extra_iam_policy: "" }, - "research-delight" : { + "researchdelight" : { requestor_pays: true, - bucket_admin_access: ["scratch-research-delight"], + bucket_admin_access: ["scratch-researchdelight"], extra_iam_policy: "" }, } From 4e22fa56c48442a9733ffda68e984ed72e172b33 Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Fri, 2 Dec 2022 18:06:39 +0000 Subject: [PATCH 013/105] Add bucket annotation to staging config --- config/clusters/2i2c-aws-us/staging.values.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/clusters/2i2c-aws-us/staging.values.yaml b/config/clusters/2i2c-aws-us/staging.values.yaml index e382a035a2..21d190a358 100644 --- a/config/clusters/2i2c-aws-us/staging.values.yaml +++ b/config/clusters/2i2c-aws-us/staging.values.yaml @@ -1,3 +1,6 @@ +userServiceAccount: + annotations: + eks.amazonaws.com/role-arn: arn:aws:iam::790657130469:role/2i2c-aws-us-staging nfs: pv: # from https://docs.aws.amazon.com/efs/latest/ug/mounting-fs-nfs-mount-settings.html From c074c28fd0edf75c5e6ce465bab9bd76f3998b48 Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Mon, 5 Dec 2022 10:59:49 +0000 Subject: [PATCH 014/105] Add config for a dask-staging hub --- config/clusters/2i2c-aws-us/cluster.yaml | 9 +++ .../2i2c-aws-us/dask-staging.values.yaml | 55 +++++++++++++++++++ .../enc-dask-staging.secret.values.yaml | 21 +++++++ 3 files changed, 85 insertions(+) create mode 100644 config/clusters/2i2c-aws-us/dask-staging.values.yaml create mode 100644 config/clusters/2i2c-aws-us/enc-dask-staging.secret.values.yaml diff --git a/config/clusters/2i2c-aws-us/cluster.yaml b/config/clusters/2i2c-aws-us/cluster.yaml index c9eeb9a3ad..356ca89a6f 100644 --- a/config/clusters/2i2c-aws-us/cluster.yaml +++ b/config/clusters/2i2c-aws-us/cluster.yaml @@ -18,3 +18,12 @@ hubs: connection: google-oauth2 helm_chart_values_files: - staging.values.yaml + - name: dask-staging + display_name: "2i2c AWS dask-staging" + domain: dask-staging.aws.2i2c.cloud + helm_chart: daskhub + auth0: + enabled: false + helm_chart_values_files: + - dask-staging.values.yaml + - enc-dask-staging.secret.values.yaml diff --git a/config/clusters/2i2c-aws-us/dask-staging.values.yaml b/config/clusters/2i2c-aws-us/dask-staging.values.yaml new file mode 100644 index 0000000000..d7b4939e9f --- /dev/null +++ b/config/clusters/2i2c-aws-us/dask-staging.values.yaml @@ -0,0 +1,55 @@ +basehub: + userServiceAccount: + annotations: + eks.amazonaws.com/role-arn: arn:aws:iam::790657130469:role/2i2c-aws-us-dask-staging + nfs: + pv: + # from https://docs.aws.amazon.com/efs/latest/ug/mounting-fs-nfs-mount-settings.html + mountOptions: + - rsize=1048576 + - wsize=1048576 + - timeo=600 + - soft # We pick soft over hard, so NFS lockups don't lead to hung processes + - retrans=2 + - noresvport + serverIP: fs-0b70db2b65209a77d.efs.us-west-2.amazonaws.com + baseShareName: / + jupyterhub: + custom: + 2i2c: + add_staff_user_ids_to_admin_users: true + add_staff_user_ids_of_type: "google" + homepage: + templateVars: + org: + name: 2i2c Dask Staging + url: https://2i2c.org + logo_url: https://2i2c.org/media/logo.png + designed_by: + name: 2i2c + url: https://2i2c.org + operated_by: + name: 2i2c + url: https://2i2c.org + funded_by: + name: 2i2c + url: https://2i2c.org + singleuser: + image: + name: pangeo/pangeo-notebook + tag: "2022.06.02" + hub: + config: + JupyterHub: + authenticator_class: cilogon + CILogonOAuthenticator: + oauth_callback_url: "https://dask-staging.aws.2i2c.cloud/hub/oauth_callback" + # Only show the option to login with Google + shown_idps: + - https://accounts.google.com/o/oauth2/auth + allowed_idps: + http://google.com/accounts/o8/id: + username_derivation: + username_claim: "email" + allowed_domains: + - "2i2c.org" diff --git a/config/clusters/2i2c-aws-us/enc-dask-staging.secret.values.yaml b/config/clusters/2i2c-aws-us/enc-dask-staging.secret.values.yaml new file mode 100644 index 0000000000..eb8096b3f0 --- /dev/null +++ b/config/clusters/2i2c-aws-us/enc-dask-staging.secret.values.yaml @@ -0,0 +1,21 @@ +basehub: + jupyterhub: + hub: + config: + CILogonOAuthenticator: + client_id: ENC[AES256_GCM,data:BfiiGt1HfLSDODMfquDhqMomaEUaHwkqeP26O36yf+jtG5Ygz65/7HGmXXFLUQfAOLrr,iv:0pQMqJEMRB5gG6rLFkEC3wQ4gIIPZGqHddc99nHZmBg=,tag:MoQTbBW7R46WfIpnkCvtLw==,type:str] + client_secret: ENC[AES256_GCM,data:0iXNqbfl/VyjCrdlrqxi4pseJo8kU5OjTbBrsGtGq94Hu+66e7I0F0PrDEIaMhnJeeLflTC2wIjdyDeRMf7Q0BNN96QVXShiNH/P/XkSqB6Ib5PDjJ8=,iv:cOxuNCoEJr5yyEO7CLD8ImRIHTYghIWryL//cw9fknM=,tag:HkItVgFlGI4y+WEFpt7hqw==,type:str] +sops: + kms: [] + gcp_kms: + - resource_id: projects/two-eye-two-see/locations/global/keyRings/sops-keys/cryptoKeys/similar-hubs + created_at: "2022-12-05T10:44:39Z" + enc: CiUA4OM7eJ8AFQjo7QEG0fUwXYqcHq9+9DAN0AuGSbzM7+5lmVnhEkkA+0T9hRclOu/WCURrpLEl8kNNUT8G5Nz0bARjq5Sji4gbyWzDcryqOJA4WewJ3dDFRRtNUP5febFlyA+uh99+RRanK3FDyjEz + azure_kv: [] + hc_vault: [] + age: [] + lastmodified: "2022-12-05T10:44:40Z" + mac: ENC[AES256_GCM,data:FSgZ25Taj9528GUOhHMHRvrNTa7NHTngGnA3fyInZvLYxrD5JEpcEsfhOMYqenLKF5BfvN6jEWhORmL+baCNiz7luWgDuQQPsRZgZcmvj8H9lgWSn/xXcTrzRidWiMfYbDMDEXCJX34vd4seaPAxRfIlgv4ObeHjZYLI9ATWmDA=,iv:MpIuzaHsue858Qs8OMk0iwE/w1J5e28gg88mQpG6GvU=,tag:u/z+s6x8xVW5L2RjhMVpjA==,type:str] + pgp: [] + unencrypted_suffix: _unencrypted + version: 3.7.3 From 2f9f8345472e239c0774750e31ed2579b70477b6 Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Mon, 5 Dec 2022 15:24:11 +0000 Subject: [PATCH 015/105] Add config for researchdelight hub --- config/clusters/2i2c-aws-us/cluster.yaml | 8 ++++ .../2i2c-aws-us/researchdelight.values.yaml | 46 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 config/clusters/2i2c-aws-us/researchdelight.values.yaml diff --git a/config/clusters/2i2c-aws-us/cluster.yaml b/config/clusters/2i2c-aws-us/cluster.yaml index 356ca89a6f..a862144e61 100644 --- a/config/clusters/2i2c-aws-us/cluster.yaml +++ b/config/clusters/2i2c-aws-us/cluster.yaml @@ -27,3 +27,11 @@ hubs: helm_chart_values_files: - dask-staging.values.yaml - enc-dask-staging.secret.values.yaml + - name: researchdelight + display_name: "2i2c Research Delight" + domain: researchdelight.2i2c.cloud + helm_chart: daskhub + auth0: + connection: github + helm_chart_values_files: + - researchdelight.values.yaml diff --git a/config/clusters/2i2c-aws-us/researchdelight.values.yaml b/config/clusters/2i2c-aws-us/researchdelight.values.yaml new file mode 100644 index 0000000000..9dff622053 --- /dev/null +++ b/config/clusters/2i2c-aws-us/researchdelight.values.yaml @@ -0,0 +1,46 @@ +basehub: + userServiceAccount: + annotations: + eks.amazonaws.com/role-arn: arn:aws:iam::790657130469:role/2i2c-aws-us-researchdelight + nfs: + pv: + # from https://docs.aws.amazon.com/efs/latest/ug/mounting-fs-nfs-mount-settings.html + mountOptions: + - rsize=1048576 + - wsize=1048576 + - timeo=600 + - soft # We pick soft over hard, so NFS lockups don't lead to hung processes + - retrans=2 + - noresvport + serverIP: fs-0b70db2b65209a77d.efs.us-west-2.amazonaws.com + baseShareName: / + jupyterhub: + custom: + 2i2c: + add_staff_user_ids_to_admin_users: true + add_staff_user_ids_of_type: "github" + homepage: + templateVars: + org: + name: 2i2c Research Delight + url: https://2i2c.org + logo_url: https://2i2c.org/media/logo.png + designed_by: + name: 2i2c + url: https://2i2c.org + operated_by: + name: 2i2c + url: https://2i2c.org + funded_by: + name: 2i2c + url: https://2i2c.org + singleuser: + image: + name: quay.io/2i2c/researchdelight-image + tag: "872f0c4578af" + hub: + config: + Authenticator: + allowed_users: &allowed_users + - jmunroe + admin_users: *allowed_users From b669ef2bc202ce41fe08a3e1cd25c21b92882a9e Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Mon, 5 Dec 2022 16:56:58 +0000 Subject: [PATCH 016/105] Add profileList options --- .../2i2c-aws-us/researchdelight.values.yaml | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/config/clusters/2i2c-aws-us/researchdelight.values.yaml b/config/clusters/2i2c-aws-us/researchdelight.values.yaml index 9dff622053..684e47374b 100644 --- a/config/clusters/2i2c-aws-us/researchdelight.values.yaml +++ b/config/clusters/2i2c-aws-us/researchdelight.values.yaml @@ -38,6 +38,40 @@ basehub: image: name: quay.io/2i2c/researchdelight-image tag: "872f0c4578af" + profileList: + # The mem-guarantees are here so k8s doesn't schedule other pods + # on these nodes. + - display_name: "Small: m5.large" + description: "~2 CPU, ~8G RAM" + default: true + kubespawner_override: + # Explicitly unset mem_limit, so it overrides the default memory limit we set in + # basehub/values.yaml + mem_limit: 8G + mem_guarantee: 6.5G + node_selector: + node.kubernetes.io/instance-type: m5.large + - display_name: "Medium: m5.xlarge" + description: "~4 CPU, ~15G RAM" + kubespawner_override: + mem_limit: 15G + mem_guarantee: 12G + node_selector: + node.kubernetes.io/instance-type: m5.xlarge + - display_name: "Large: m5.2xlarge" + description: "~8 CPU, ~30G RAM" + kubespawner_override: + mem_limit: 30G + mem_guarantee: 25G + node_selector: + node.kubernetes.io/instance-type: m5.2xlarge + - display_name: "Huge: m5.8xlarge" + description: "~16 CPU, ~60G RAM" + kubespawner_override: + mem_limit: 60G + mem_guarantee: 50G + node_selector: + node.kubernetes.io/instance-type: m5.8xlarge hub: config: Authenticator: From 3aa005ad6004b843ca3c2dcfdf8ba51321c9566c Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Fri, 9 Dec 2022 14:05:25 +0000 Subject: [PATCH 017/105] Add GPU machines to notebook nodes --- eksctl/2i2c-aws-us.jsonnet | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/eksctl/2i2c-aws-us.jsonnet b/eksctl/2i2c-aws-us.jsonnet index 71ce93ee85..249250abc8 100644 --- a/eksctl/2i2c-aws-us.jsonnet +++ b/eksctl/2i2c-aws-us.jsonnet @@ -15,6 +15,12 @@ local notebookNodes = [ { instanceType: "m5.xlarge" }, { instanceType: "m5.2xlarge" }, { instanceType: "m5.8xlarge" }, + { + instanceType: "g4dn.xlarge", + tags+: { + "k8s.io/cluster-autoscaler/node-template/resources/nvidia.com/gpu": "1" + }, + }, ]; local daskNodes = From 8355ef6692a1cad4157c9e24cb46469f692ad1ae Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Fri, 9 Dec 2022 14:31:33 +0000 Subject: [PATCH 018/105] Add GPU profile options to research delight hub --- .../2i2c-aws-us/researchdelight.values.yaml | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/config/clusters/2i2c-aws-us/researchdelight.values.yaml b/config/clusters/2i2c-aws-us/researchdelight.values.yaml index 684e47374b..07cb017bae 100644 --- a/config/clusters/2i2c-aws-us/researchdelight.values.yaml +++ b/config/clusters/2i2c-aws-us/researchdelight.values.yaml @@ -38,6 +38,10 @@ basehub: image: name: quay.io/2i2c/researchdelight-image tag: "872f0c4578af" + extraEnv: + # Temporarily set for *all* pods, including pods without any GPUs, + # to work around https://github.com/2i2c-org/infrastructure/issues/1530 + NVIDIA_DRIVER_CAPABILITIES: compute,utility profileList: # The mem-guarantees are here so k8s doesn't schedule other pods # on these nodes. @@ -72,6 +76,30 @@ basehub: mem_guarantee: 50G node_selector: node.kubernetes.io/instance-type: m5.8xlarge + - display_name: "Large + GPU" + description: "14GB RAM, 4 CPUs, T4 GPU" + profile_options: + image: + display_name: Image + choices: + tensorflow: + display_name: Pangeo Tensorflow ML Notebook + slug: "tensorflow" + kubespawner_override: + node.kubernetes.io/instance-type: g4dn.xlarge + image: "pangeo/ml-notebook:b9584f6" + pytorch: + display_name: Pangeo PyTorch ML Notebook + default: true + slug: "pytorch" + kubespawner_override: + node.kubernetes.io/instance-type: g4dn.xlarge + image: "pangeo/pytorch-notebook:b9584f6" + kubespawner_override: + mem_limit: null + mem_guarantee: 14G + extra_resource_limits: + nvidia.com/gpu: "1" hub: config: Authenticator: From fbe186b051c113943193d0a0134dd040bb5bf281 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 9 Dec 2022 14:34:41 +0000 Subject: [PATCH 019/105] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- config/clusters/2i2c-aws-us/researchdelight.values.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/clusters/2i2c-aws-us/researchdelight.values.yaml b/config/clusters/2i2c-aws-us/researchdelight.values.yaml index 07cb017bae..fcb3a69891 100644 --- a/config/clusters/2i2c-aws-us/researchdelight.values.yaml +++ b/config/clusters/2i2c-aws-us/researchdelight.values.yaml @@ -39,9 +39,9 @@ basehub: name: quay.io/2i2c/researchdelight-image tag: "872f0c4578af" extraEnv: - # Temporarily set for *all* pods, including pods without any GPUs, - # to work around https://github.com/2i2c-org/infrastructure/issues/1530 - NVIDIA_DRIVER_CAPABILITIES: compute,utility + # Temporarily set for *all* pods, including pods without any GPUs, + # to work around https://github.com/2i2c-org/infrastructure/issues/1530 + NVIDIA_DRIVER_CAPABILITIES: compute,utility profileList: # The mem-guarantees are here so k8s doesn't schedule other pods # on these nodes. From ac5b24bdff56490ff9b7e657ebc75ff5661061b8 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Wed, 30 Nov 2022 13:34:32 +0200 Subject: [PATCH 020/105] Move allowNamedServers under hub config --- config/clusters/nasa-cryo/common.values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/clusters/nasa-cryo/common.values.yaml b/config/clusters/nasa-cryo/common.values.yaml index 9bc6312070..1de9d35399 100644 --- a/config/clusters/nasa-cryo/common.values.yaml +++ b/config/clusters/nasa-cryo/common.values.yaml @@ -35,6 +35,7 @@ basehub: name: "NASA ICESat-2 Science Team" url: https://icesat-2.gsfc.nasa.gov/science_definition_team hub: + allowNamedServers: true config: Authenticator: # We are restricting profiles based on GitHub Team membership and @@ -61,7 +62,6 @@ basehub: scope: - read:org singleuser: - allowNamedServers: true defaultUrl: /lab # Image repo: https://github.com/CryoInTheCloud/hub-image image: From 840b175ff284d09d34b919fd7a0e227d19e82591 Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Wed, 30 Nov 2022 11:58:21 +0000 Subject: [PATCH 021/105] Ask to provide a time conversion link in event template This help members of the team translate into their own timezone --- .github/ISSUE_TEMPLATE/6_event-hub.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/6_event-hub.md b/.github/ISSUE_TEMPLATE/6_event-hub.md index d546d2624f..f367f62e4d 100644 --- a/.github/ISSUE_TEMPLATE/6_event-hub.md +++ b/.github/ISSUE_TEMPLATE/6_event-hub.md @@ -17,8 +17,11 @@ assignees: '' - **Community Representative:** - **Event begin:** + - **In your timezone:** - **Event end:** + - **In your timezone:** - **Active times:** + - **In your timezone:** - **Number of attendees:** - [**Hub Events Calendar**](https://calendar.google.com/calendar/u/2?cid=Y19rdDg0c2g3YW5tMHNsb2NqczJzdTNqdnNvY0Bncm91cC5jYWxlbmRhci5nb29nbGUuY29t) From ee936006266eac8379d55de1e5af2d99457b5c20 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Thu, 17 Nov 2022 13:40:50 +0200 Subject: [PATCH 022/105] Add the project id as input also --- deployer/generate_cluster.py | 14 +++++++++----- terraform/gcp/projects/basehub-template.tfvars | 2 +- terraform/gcp/projects/daskhub-template.tfvars | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/deployer/generate_cluster.py b/deployer/generate_cluster.py index 31d8c06739..11bcac24ed 100644 --- a/deployer/generate_cluster.py +++ b/deployer/generate_cluster.py @@ -76,7 +76,7 @@ def aws(cluster_name, hub_type, cluster_region): ) -def gcp(cluster_name, hub_type, cluster_region, cluster_zone): +def gcp(cluster_name, cluster_region, cluster_zone, project_id, hub_type): """ Generates the cluster_name.tfvars terraform file required to create a GCP cluster @@ -89,6 +89,7 @@ def gcp(cluster_name, hub_type, cluster_region, cluster_zone): "cluster_name": cluster_name, "cluster_region": cluster_region, "cluster_zone": cluster_zone, + "project_id": project_id, } with open( @@ -116,17 +117,20 @@ def generate_aws_cluster( @app.command() def generate_gcp_cluster( cluster_name: str = typer.Option(..., prompt="Name of the cluster to deploy"), - hub_type: str = typer.Option( - ..., prompt="Type of hub. Choose from `basehub` or `daskhub`" - ), cluster_region: str = typer.Option( ..., prompt="The region where to deploy the cluster" ), cluster_zone: str = typer.Option( ..., prompt="The zone where to deploy the cluster, eg. us-west2-b" ), + project_id: str = typer.Option( + ..., prompt="The GCP Project ID to create resources in" + ), + hub_type: str = typer.Option( + ..., prompt="Type of hub. Choose from `basehub` or `daskhub`" + ), ): """ Automatically generate the terraform config file required to setup a new cluster on GCP """ - gcp(cluster_name, hub_type, cluster_region, cluster_zone) + gcp(cluster_name, cluster_region, cluster_zone, project_id, hub_type) diff --git a/terraform/gcp/projects/basehub-template.tfvars b/terraform/gcp/projects/basehub-template.tfvars index cc2a372345..9bcef79a23 100644 --- a/terraform/gcp/projects/basehub-template.tfvars +++ b/terraform/gcp/projects/basehub-template.tfvars @@ -7,7 +7,7 @@ */ prefix = "{{ cluster_name }}" -project_id = "{{ cluster_name }}" +project_id = "{{ project_id }}" zone = "{{ cluster_zone }}" region = "{{ cluster_region }}" diff --git a/terraform/gcp/projects/daskhub-template.tfvars b/terraform/gcp/projects/daskhub-template.tfvars index cc354da7eb..7dff049afc 100644 --- a/terraform/gcp/projects/daskhub-template.tfvars +++ b/terraform/gcp/projects/daskhub-template.tfvars @@ -7,7 +7,7 @@ */ prefix = "{{ cluster_name }}" -project_id = "{{ cluster_name }}" +project_id = "{{ project_id }}" zone = "{{ cluster_zone }}" region = "{{ cluster_region }}" From 9949c81a802cacc20f103fd3fec4a0241df2c2ea Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Mon, 21 Nov 2022 07:49:55 +0200 Subject: [PATCH 023/105] Add a generator for cluster initial config files --- deployer/__main__.py | 1 + deployer/generate_hub.py | 102 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 deployer/generate_hub.py diff --git a/deployer/__main__.py b/deployer/__main__.py index 7d1cddd578..5b534e2f5d 100644 --- a/deployer/__main__.py +++ b/deployer/__main__.py @@ -4,6 +4,7 @@ import deployer.debug # noqa: F401 import deployer.deployer # noqa: F401 import deployer.generate_cluster # noqa: F401 +import deployer.generate_hub # noqa: F401 from .cli_app import app diff --git a/deployer/generate_hub.py b/deployer/generate_hub.py new file mode 100644 index 0000000000..9bcddb7728 --- /dev/null +++ b/deployer/generate_hub.py @@ -0,0 +1,102 @@ +import os +import secrets +import string +import subprocess +from pathlib import Path + +import jinja2 +import typer + +from .cli_app import app +from .utils import print_colour + +REPO_ROOT = Path(__file__).parent.parent + + +def gcp_config_files(cluster_name, cluster_region, project_id, hub_type, hub_name): + """ + Generate the required `config` directory and files for hubs on a GCP cluster + + Generates in the directory: + - cluster.yaml file + - support.values.yaml file + - enc-support.secret.values.yaml file + """ + + cluster_config_directory = REPO_ROOT / "config/clusters" / cluster_name + + vars = { + "cluster_name": cluster_name, + "hub_type": hub_type, + "cluster_region": cluster_region, + "project_id": project_id, + "hub_name": hub_name + } + + print_colour("Checking if cluster config directory {cluster_config_directory} exists...", "yellow") + if not os.path.exists(cluster_config_directory): + os.makedirs(cluster_config_directory) + print_colour(f"Done!") + + with open(REPO_ROOT / "config/clusters/templates/gcp/cluster.yaml") as f: + cluster_yaml_template = jinja2.Template(f.read()) + with open( + cluster_config_directory / "cluster.yaml", "w" + ) as f: + f.write(cluster_yaml_template.render(**vars)) + else: + print_colour(f"Cluster config directory already exists -> {cluster_config_directory}.", "yellow") + return + + print_colour("Generating the support values file...", "yellow") + with open(REPO_ROOT / "config/clusters/templates/gcp/support.values.yaml") as f: + support_values_yaml_template = jinja2.Template(f.read()) + with open( + cluster_config_directory / "support.values.yaml", "w" + ) as f: + f.write(support_values_yaml_template.render(**vars)) + print_colour("Done!") + + print_colour("Generating the prometheus credentials encrypted file...", "yellow") + alphabet = string.ascii_letters + string.digits + credentials = { + "username": ''.join(secrets.choice(alphabet) for i in range(64)), + "password": ''.join(secrets.choice(alphabet) for i in range(64)) + } + with open(REPO_ROOT / "config/clusters/templates/gcp/support.secret.values.yaml") as f: + support_secret_values_yaml_template = jinja2.Template(f.read()) + with open( + cluster_config_directory / "enc-support.secret.values.yaml", "w" + ) as f: + f.write(support_secret_values_yaml_template.render(**credentials)) + + # Encrypt the private key + subprocess.check_call( + [ + "sops", + "--in-place", + "--encrypt", + cluster_config_directory / "enc-support.secret.values.yaml" + ] + ) + +@app.command() +def generate_gcp_config_files( + cluster_name: str = typer.Option(..., prompt="Name of the cluster where the hub will be deployed"), + cluster_region: str = typer.Option( + ..., prompt="The region the cluster is or will be in" + ), + project_id: str = typer.Option( + ..., prompt="Project ID of the GCP project that contains this cluster" + ), + hub_type: str = typer.Option( + ..., prompt="Type of hub. Choose from `basehub` or `daskhub`" + ), + hub_name: str = typer.Option( + ..., prompt="Name of the hub" + ), +): + """ + Automatically generate the terraform config file required to setup a new cluster on GCP + """ + gcp_config_files(cluster_name, cluster_region, project_id, hub_type, hub_name) From 63a6eabfcafe1f105353e03f2ab6ecc4ea066e11 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Mon, 21 Nov 2022 08:00:36 +0200 Subject: [PATCH 024/105] Move all gcp generators into one file --- deployer/generate_cluster.py | 44 ------------------------------------ deployer/generate_hub.py | 32 ++++++++++++++++++++++---- 2 files changed, 28 insertions(+), 48 deletions(-) diff --git a/deployer/generate_cluster.py b/deployer/generate_cluster.py index 11bcac24ed..8c525d4f76 100644 --- a/deployer/generate_cluster.py +++ b/deployer/generate_cluster.py @@ -76,28 +76,6 @@ def aws(cluster_name, hub_type, cluster_region): ) -def gcp(cluster_name, cluster_region, cluster_zone, project_id, hub_type): - """ - Generates the cluster_name.tfvars terraform file - required to create a GCP cluster - """ - - with open(REPO_ROOT / f"terraform/gcp/projects/{hub_type}-template.tfvars") as f: - tfvars_template = jinja2.Template(f.read()) - - vars = { - "cluster_name": cluster_name, - "cluster_region": cluster_region, - "cluster_zone": cluster_zone, - "project_id": project_id, - } - - with open( - REPO_ROOT / "terraform/gcp/projects" / f"{cluster_name}.tfvars", "w" - ) as f: - f.write(tfvars_template.render(**vars)) - - @app.command() def generate_aws_cluster( cluster_name: str = typer.Option(..., prompt="Name of the cluster to deploy"), @@ -112,25 +90,3 @@ def generate_aws_cluster( Automatically generate the files required to setup a new cluster on AWS """ aws(cluster_name, hub_type, cluster_region) - - -@app.command() -def generate_gcp_cluster( - cluster_name: str = typer.Option(..., prompt="Name of the cluster to deploy"), - cluster_region: str = typer.Option( - ..., prompt="The region where to deploy the cluster" - ), - cluster_zone: str = typer.Option( - ..., prompt="The zone where to deploy the cluster, eg. us-west2-b" - ), - project_id: str = typer.Option( - ..., prompt="The GCP Project ID to create resources in" - ), - hub_type: str = typer.Option( - ..., prompt="Type of hub. Choose from `basehub` or `daskhub`" - ), -): - """ - Automatically generate the terraform config file required to setup a new cluster on GCP - """ - gcp(cluster_name, cluster_region, cluster_zone, project_id, hub_type) diff --git a/deployer/generate_hub.py b/deployer/generate_hub.py index 9bcddb7728..777e291f28 100644 --- a/deployer/generate_hub.py +++ b/deployer/generate_hub.py @@ -13,11 +13,32 @@ REPO_ROOT = Path(__file__).parent.parent +def gcp_infrastructure_files(cluster_name, cluster_region, project_id, hub_type): + """ + Generates the cluster_name.tfvars terraform file + required to create a GCP cluster + """ + + with open(REPO_ROOT / f"terraform/gcp/projects/{hub_type}-template.tfvars") as f: + tfvars_template = jinja2.Template(f.read()) + + vars = { + "cluster_name": cluster_name, + "cluster_region": cluster_region, + "project_id": project_id, + } + + with open( + REPO_ROOT / "terraform/gcp/projects" / f"{cluster_name}.tfvars", "w" + ) as f: + f.write(tfvars_template.render(**vars)) + + def gcp_config_files(cluster_name, cluster_region, project_id, hub_type, hub_name): """ - Generate the required `config` directory and files for hubs on a GCP cluster + Generates the required `config` directory and files for hubs on a GCP cluster - Generates in the directory: + Generates the following files, in the directory: - cluster.yaml file - support.values.yaml file - enc-support.secret.values.yaml file @@ -81,7 +102,7 @@ def gcp_config_files(cluster_name, cluster_region, project_id, hub_type, hub_nam ) @app.command() -def generate_gcp_config_files( +def generate_gcp_cluster( cluster_name: str = typer.Option(..., prompt="Name of the cluster where the hub will be deployed"), cluster_region: str = typer.Option( ..., prompt="The region the cluster is or will be in" @@ -97,6 +118,9 @@ def generate_gcp_config_files( ), ): """ - Automatically generate the terraform config file required to setup a new cluster on GCP """ + # Automatically generate the terraform config file required to setup a new cluster on GCP + gcp_infrastructure_files(cluster_name, cluster_region, project_id, hub_type) + + # Automatically generate the config directory required to setup a new cluster on GCP gcp_config_files(cluster_name, cluster_region, project_id, hub_type, hub_name) From 8f80ba95ac7b560a179366b76c43abc8a4d1ffea Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Mon, 21 Nov 2022 08:00:54 +0200 Subject: [PATCH 025/105] GCP cluster is regional by default --- terraform/gcp/projects/basehub-template.tfvars | 2 +- terraform/gcp/projects/daskhub-template.tfvars | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/terraform/gcp/projects/basehub-template.tfvars b/terraform/gcp/projects/basehub-template.tfvars index 9bcef79a23..9a33e87715 100644 --- a/terraform/gcp/projects/basehub-template.tfvars +++ b/terraform/gcp/projects/basehub-template.tfvars @@ -9,7 +9,7 @@ prefix = "{{ cluster_name }}" project_id = "{{ project_id }}" -zone = "{{ cluster_zone }}" +zone = "{{ cluster_region }}" region = "{{ cluster_region }}" # Default to a HA cluster for reliability diff --git a/terraform/gcp/projects/daskhub-template.tfvars b/terraform/gcp/projects/daskhub-template.tfvars index 7dff049afc..ca7d5cafb6 100644 --- a/terraform/gcp/projects/daskhub-template.tfvars +++ b/terraform/gcp/projects/daskhub-template.tfvars @@ -9,7 +9,7 @@ prefix = "{{ cluster_name }}" project_id = "{{ project_id }}" -zone = "{{ cluster_zone }}" +zone = "{{ cluster_region }}" region = "{{ cluster_region }}" # Default to a HA cluster for reliability From 31ccf2672b5a6a69c054371ff8835198576bc7b6 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Mon, 21 Nov 2022 08:02:21 +0200 Subject: [PATCH 026/105] Rename --- deployer/{generate_cluster.py => generate_aws_cluster.py} | 0 deployer/{generate_hub.py => generate_gcp_cluster.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename deployer/{generate_cluster.py => generate_aws_cluster.py} (100%) rename deployer/{generate_hub.py => generate_gcp_cluster.py} (100%) diff --git a/deployer/generate_cluster.py b/deployer/generate_aws_cluster.py similarity index 100% rename from deployer/generate_cluster.py rename to deployer/generate_aws_cluster.py diff --git a/deployer/generate_hub.py b/deployer/generate_gcp_cluster.py similarity index 100% rename from deployer/generate_hub.py rename to deployer/generate_gcp_cluster.py From a777d192a06a3b11c8a5df534778ac7e7a04a03c Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Mon, 21 Nov 2022 08:19:16 +0200 Subject: [PATCH 027/105] Refactor a bit --- deployer/__main__.py | 4 ++-- deployer/generate_gcp_cluster.py | 25 ++++++++++++++++--------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/deployer/__main__.py b/deployer/__main__.py index 5b534e2f5d..392a1b02a8 100644 --- a/deployer/__main__.py +++ b/deployer/__main__.py @@ -3,8 +3,8 @@ import deployer.central_grafana # noqa: F401 import deployer.debug # noqa: F401 import deployer.deployer # noqa: F401 -import deployer.generate_cluster # noqa: F401 -import deployer.generate_hub # noqa: F401 +import deployer.generate_gcp_cluster # noqa: F401 +import deployer.generate_aws_cluster # noqa: F401 from .cli_app import app diff --git a/deployer/generate_gcp_cluster.py b/deployer/generate_gcp_cluster.py index 777e291f28..5484dc50da 100644 --- a/deployer/generate_gcp_cluster.py +++ b/deployer/generate_gcp_cluster.py @@ -28,10 +28,12 @@ def gcp_infrastructure_files(cluster_name, cluster_region, project_id, hub_type) "project_id": project_id, } + print_colour("Generating the terraform infrastructure file...", "yellow") with open( REPO_ROOT / "terraform/gcp/projects" / f"{cluster_name}.tfvars", "w" ) as f: f.write(tfvars_template.render(**vars)) + print_colour(f"{REPO_ROOT}/terraform/gcp/projects/{cluster_name}.tfvars created") def gcp_config_files(cluster_name, cluster_region, project_id, hub_type, hub_name): @@ -54,10 +56,11 @@ def gcp_config_files(cluster_name, cluster_region, project_id, hub_type, hub_nam "hub_name": hub_name } + # Create the cluster config directory and initial `cluster.yaml` file print_colour("Checking if cluster config directory {cluster_config_directory} exists...", "yellow") if not os.path.exists(cluster_config_directory): os.makedirs(cluster_config_directory) - print_colour(f"Done!") + print_colour(f"{cluster_config_directory} created") with open(REPO_ROOT / "config/clusters/templates/gcp/cluster.yaml") as f: cluster_yaml_template = jinja2.Template(f.read()) @@ -66,9 +69,10 @@ def gcp_config_files(cluster_name, cluster_region, project_id, hub_type, hub_nam ) as f: f.write(cluster_yaml_template.render(**vars)) else: - print_colour(f"Cluster config directory already exists -> {cluster_config_directory}.", "yellow") + print_colour(f"{cluster_config_directory} already exists.") return + # Generate the suppport values file `support.values.yaml` print_colour("Generating the support values file...", "yellow") with open(REPO_ROOT / "config/clusters/templates/gcp/support.values.yaml") as f: support_values_yaml_template = jinja2.Template(f.read()) @@ -76,8 +80,9 @@ def gcp_config_files(cluster_name, cluster_region, project_id, hub_type, hub_nam cluster_config_directory / "support.values.yaml", "w" ) as f: f.write(support_values_yaml_template.render(**vars)) - print_colour("Done!") + print_colour(f"{cluster_config_directory}/support.values.yaml created") + # Generate and encrypt prometheus credentials into `enc-support.secret.values.yaml` print_colour("Generating the prometheus credentials encrypted file...", "yellow") alphabet = string.ascii_letters + string.digits credentials = { @@ -100,27 +105,29 @@ def gcp_config_files(cluster_name, cluster_region, project_id, hub_type, hub_nam cluster_config_directory / "enc-support.secret.values.yaml" ] ) + print_colour(f"{cluster_config_directory}/enc-support.values.yaml created and encrypted") @app.command() def generate_gcp_cluster( - cluster_name: str = typer.Option(..., prompt="Name of the cluster where the hub will be deployed"), + cluster_name: str = typer.Option(..., prompt="Name of the cluster"), cluster_region: str = typer.Option( - ..., prompt="The region the cluster is or will be in" + ..., prompt="Cluster region" ), project_id: str = typer.Option( - ..., prompt="Project ID of the GCP project that contains this cluster" + ..., prompt="Project ID of the GCP project" ), hub_type: str = typer.Option( ..., prompt="Type of hub. Choose from `basehub` or `daskhub`" ), hub_name: str = typer.Option( - ..., prompt="Name of the hub" + ..., prompt="Name of the first hub" ), ): """ + Automatically generates the default intitial files required to setup a new cluster on GCP """ - # Automatically generate the terraform config file required to setup a new cluster on GCP + # Automatically generate the terraform config file gcp_infrastructure_files(cluster_name, cluster_region, project_id, hub_type) - # Automatically generate the config directory required to setup a new cluster on GCP + # Automatically generate the config directory gcp_config_files(cluster_name, cluster_region, project_id, hub_type, hub_name) From a48947298271b64862f3f62a3171aa8ed6f41a6f Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Mon, 21 Nov 2022 11:30:14 +0200 Subject: [PATCH 028/105] Better separate functionality --- deployer/generate_gcp_cluster.py | 84 +++++++++++++++++--------------- 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/deployer/generate_gcp_cluster.py b/deployer/generate_gcp_cluster.py index 5484dc50da..23018250bf 100644 --- a/deployer/generate_gcp_cluster.py +++ b/deployer/generate_gcp_cluster.py @@ -13,7 +13,7 @@ REPO_ROOT = Path(__file__).parent.parent -def gcp_infrastructure_files(cluster_name, cluster_region, project_id, hub_type): +def generate_terraform_file(cluster_name, cluster_region, project_id, hub_type): """ Generates the cluster_name.tfvars terraform file required to create a GCP cluster @@ -35,43 +35,15 @@ def gcp_infrastructure_files(cluster_name, cluster_region, project_id, hub_type) f.write(tfvars_template.render(**vars)) print_colour(f"{REPO_ROOT}/terraform/gcp/projects/{cluster_name}.tfvars created") +def generate_cluster_config_file(cluster_config_directory, vars): + with open(REPO_ROOT / "config/clusters/templates/gcp/cluster.yaml") as f: + cluster_yaml_template = jinja2.Template(f.read()) + with open( + cluster_config_directory / "cluster.yaml", "w" + ) as f: + f.write(cluster_yaml_template.render(**vars)) -def gcp_config_files(cluster_name, cluster_region, project_id, hub_type, hub_name): - """ - Generates the required `config` directory and files for hubs on a GCP cluster - - Generates the following files, in the directory: - - cluster.yaml file - - support.values.yaml file - - enc-support.secret.values.yaml file - """ - - cluster_config_directory = REPO_ROOT / "config/clusters" / cluster_name - - vars = { - "cluster_name": cluster_name, - "hub_type": hub_type, - "cluster_region": cluster_region, - "project_id": project_id, - "hub_name": hub_name - } - - # Create the cluster config directory and initial `cluster.yaml` file - print_colour("Checking if cluster config directory {cluster_config_directory} exists...", "yellow") - if not os.path.exists(cluster_config_directory): - os.makedirs(cluster_config_directory) - print_colour(f"{cluster_config_directory} created") - - with open(REPO_ROOT / "config/clusters/templates/gcp/cluster.yaml") as f: - cluster_yaml_template = jinja2.Template(f.read()) - with open( - cluster_config_directory / "cluster.yaml", "w" - ) as f: - f.write(cluster_yaml_template.render(**vars)) - else: - print_colour(f"{cluster_config_directory} already exists.") - return - +def generate_support_files(cluster_config_directory, vars): # Generate the suppport values file `support.values.yaml` print_colour("Generating the support values file...", "yellow") with open(REPO_ROOT / "config/clusters/templates/gcp/support.values.yaml") as f: @@ -107,6 +79,40 @@ def gcp_config_files(cluster_name, cluster_region, project_id, hub_type, hub_nam ) print_colour(f"{cluster_config_directory}/enc-support.values.yaml created and encrypted") + +def generate_config_directory(cluster_name, cluster_region, project_id, hub_type, hub_name): + """ + Generates the required `config` directory and files for hubs on a GCP cluster + + Generates the following files, in the directory: + - cluster.yaml file + - support.values.yaml file + - enc-support.secret.values.yaml file + """ + + cluster_config_directory = REPO_ROOT / "config/clusters" / cluster_name + + vars = { + "cluster_name": cluster_name, + "hub_type": hub_type, + "cluster_region": cluster_region, + "project_id": project_id, + "hub_name": hub_name + } + + print_colour("Checking if cluster config directory {cluster_config_directory} exists...", "yellow") + if os.path.exists(cluster_config_directory): + print_colour(f"{cluster_config_directory} already exists.") + return + + # Create the cluster config directory and initial `cluster.yaml` file + os.makedirs(cluster_config_directory) + print_colour(f"{cluster_config_directory} created") + generate_cluster_config_file(cluster_config_directory, vars) + + # Generate the support files + generate_support_files(cluster_config_directory, vars) + @app.command() def generate_gcp_cluster( cluster_name: str = typer.Option(..., prompt="Name of the cluster"), @@ -127,7 +133,7 @@ def generate_gcp_cluster( Automatically generates the default intitial files required to setup a new cluster on GCP """ # Automatically generate the terraform config file - gcp_infrastructure_files(cluster_name, cluster_region, project_id, hub_type) + generate_terraform_file(cluster_name, cluster_region, project_id, hub_type) # Automatically generate the config directory - gcp_config_files(cluster_name, cluster_region, project_id, hub_type, hub_name) + generate_config_directory(cluster_name, cluster_region, project_id, hub_type, hub_name) From 0a4aa71828abf7a5c72e9f7b89e5c9891b59cd24 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 21 Nov 2022 10:05:13 +0000 Subject: [PATCH 029/105] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- deployer/__main__.py | 2 +- deployer/generate_gcp_cluster.py | 54 +++++++++++++++++--------------- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/deployer/__main__.py b/deployer/__main__.py index 392a1b02a8..e543b05352 100644 --- a/deployer/__main__.py +++ b/deployer/__main__.py @@ -3,8 +3,8 @@ import deployer.central_grafana # noqa: F401 import deployer.debug # noqa: F401 import deployer.deployer # noqa: F401 +import deployer.generate_aws_cluster # noqa: F401 import deployer.generate_gcp_cluster # noqa: F401 -import deployer.generate_aws_cluster # noqa: F401 from .cli_app import app diff --git a/deployer/generate_gcp_cluster.py b/deployer/generate_gcp_cluster.py index 23018250bf..304475b473 100644 --- a/deployer/generate_gcp_cluster.py +++ b/deployer/generate_gcp_cluster.py @@ -35,22 +35,20 @@ def generate_terraform_file(cluster_name, cluster_region, project_id, hub_type): f.write(tfvars_template.render(**vars)) print_colour(f"{REPO_ROOT}/terraform/gcp/projects/{cluster_name}.tfvars created") + def generate_cluster_config_file(cluster_config_directory, vars): with open(REPO_ROOT / "config/clusters/templates/gcp/cluster.yaml") as f: cluster_yaml_template = jinja2.Template(f.read()) - with open( - cluster_config_directory / "cluster.yaml", "w" - ) as f: + with open(cluster_config_directory / "cluster.yaml", "w") as f: f.write(cluster_yaml_template.render(**vars)) + def generate_support_files(cluster_config_directory, vars): # Generate the suppport values file `support.values.yaml` print_colour("Generating the support values file...", "yellow") with open(REPO_ROOT / "config/clusters/templates/gcp/support.values.yaml") as f: support_values_yaml_template = jinja2.Template(f.read()) - with open( - cluster_config_directory / "support.values.yaml", "w" - ) as f: + with open(cluster_config_directory / "support.values.yaml", "w") as f: f.write(support_values_yaml_template.render(**vars)) print_colour(f"{cluster_config_directory}/support.values.yaml created") @@ -58,14 +56,14 @@ def generate_support_files(cluster_config_directory, vars): print_colour("Generating the prometheus credentials encrypted file...", "yellow") alphabet = string.ascii_letters + string.digits credentials = { - "username": ''.join(secrets.choice(alphabet) for i in range(64)), - "password": ''.join(secrets.choice(alphabet) for i in range(64)) + "username": "".join(secrets.choice(alphabet) for i in range(64)), + "password": "".join(secrets.choice(alphabet) for i in range(64)), } - with open(REPO_ROOT / "config/clusters/templates/gcp/support.secret.values.yaml") as f: - support_secret_values_yaml_template = jinja2.Template(f.read()) with open( - cluster_config_directory / "enc-support.secret.values.yaml", "w" + REPO_ROOT / "config/clusters/templates/gcp/support.secret.values.yaml" ) as f: + support_secret_values_yaml_template = jinja2.Template(f.read()) + with open(cluster_config_directory / "enc-support.secret.values.yaml", "w") as f: f.write(support_secret_values_yaml_template.render(**credentials)) # Encrypt the private key @@ -74,13 +72,17 @@ def generate_support_files(cluster_config_directory, vars): "sops", "--in-place", "--encrypt", - cluster_config_directory / "enc-support.secret.values.yaml" + cluster_config_directory / "enc-support.secret.values.yaml", ] ) - print_colour(f"{cluster_config_directory}/enc-support.values.yaml created and encrypted") + print_colour( + f"{cluster_config_directory}/enc-support.values.yaml created and encrypted" + ) -def generate_config_directory(cluster_name, cluster_region, project_id, hub_type, hub_name): +def generate_config_directory( + cluster_name, cluster_region, project_id, hub_type, hub_name +): """ Generates the required `config` directory and files for hubs on a GCP cluster @@ -97,10 +99,13 @@ def generate_config_directory(cluster_name, cluster_region, project_id, hub_type "hub_type": hub_type, "cluster_region": cluster_region, "project_id": project_id, - "hub_name": hub_name + "hub_name": hub_name, } - print_colour("Checking if cluster config directory {cluster_config_directory} exists...", "yellow") + print_colour( + "Checking if cluster config directory {cluster_config_directory} exists...", + "yellow", + ) if os.path.exists(cluster_config_directory): print_colour(f"{cluster_config_directory} already exists.") return @@ -113,21 +118,16 @@ def generate_config_directory(cluster_name, cluster_region, project_id, hub_type # Generate the support files generate_support_files(cluster_config_directory, vars) + @app.command() def generate_gcp_cluster( cluster_name: str = typer.Option(..., prompt="Name of the cluster"), - cluster_region: str = typer.Option( - ..., prompt="Cluster region" - ), - project_id: str = typer.Option( - ..., prompt="Project ID of the GCP project" - ), + cluster_region: str = typer.Option(..., prompt="Cluster region"), + project_id: str = typer.Option(..., prompt="Project ID of the GCP project"), hub_type: str = typer.Option( ..., prompt="Type of hub. Choose from `basehub` or `daskhub`" ), - hub_name: str = typer.Option( - ..., prompt="Name of the first hub" - ), + hub_name: str = typer.Option(..., prompt="Name of the first hub"), ): """ Automatically generates the default intitial files required to setup a new cluster on GCP @@ -136,4 +136,6 @@ def generate_gcp_cluster( generate_terraform_file(cluster_name, cluster_region, project_id, hub_type) # Automatically generate the config directory - generate_config_directory(cluster_name, cluster_region, project_id, hub_type, hub_name) + generate_config_directory( + cluster_name, cluster_region, project_id, hub_type, hub_name + ) From efd65c56ad50059c108d046c2d9570ae3f60a8e4 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Mon, 21 Nov 2022 12:25:39 +0200 Subject: [PATCH 030/105] Add cluster config template files --- config/clusters/templates/gcp/cluster.yaml | 29 +++++++++++++++++++ .../templates/gcp/support.secret.values.yaml | 3 ++ .../templates/gcp/support.values.yaml | 25 ++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 config/clusters/templates/gcp/cluster.yaml create mode 100644 config/clusters/templates/gcp/support.secret.values.yaml create mode 100644 config/clusters/templates/gcp/support.values.yaml diff --git a/config/clusters/templates/gcp/cluster.yaml b/config/clusters/templates/gcp/cluster.yaml new file mode 100644 index 0000000000..33040d7aac --- /dev/null +++ b/config/clusters/templates/gcp/cluster.yaml @@ -0,0 +1,29 @@ +name: {{ cluster_name }} +provider: gcp +gcp: + key: enc-deployer-credentials.secret.json + project: {{ project_id }} + cluster: {{ cluster_name }} + # We default to a regional cluster + zone: {{ cluster_region }} +support: + helm_chart_values_files: + - support.values.yaml + - enc-support.secret.values.yaml +hubs: + - name: {{ hub_name }} + # Tip: consider changing this to something more human friendly + display_name: "{{ cluster_name }} - {{ hub_name }}" + domain: {{ hub_name }}.{{ cluster_name }}.2i2c.cloud + helm_chart: {{ hub_type }} + auth0: + # Tip: Uncomment the line below and enable auth0 + # if you want to use it for this hub. + # By default, it is disabled + # connection: github/google + enabled: false + helm_chart_values_files: + - common.values.yaml + - {{ hub_name }}.values.yaml + - enc-{{ hub_name }}.secret.values.yaml + diff --git a/config/clusters/templates/gcp/support.secret.values.yaml b/config/clusters/templates/gcp/support.secret.values.yaml new file mode 100644 index 0000000000..f2abb15fe3 --- /dev/null +++ b/config/clusters/templates/gcp/support.secret.values.yaml @@ -0,0 +1,3 @@ +prometheusIngressAuthSecret: + username: {{ username }} + password: {{ password }} \ No newline at end of file diff --git a/config/clusters/templates/gcp/support.values.yaml b/config/clusters/templates/gcp/support.values.yaml new file mode 100644 index 0000000000..4b33d599fb --- /dev/null +++ b/config/clusters/templates/gcp/support.values.yaml @@ -0,0 +1,25 @@ +prometheusIngressAuthSecret: + enabled: true + + prometheus: + server: + ingress: + enabled: true + hosts: + - prometheus.{{ cluster_name }}.2i2c.cloud + tls: + - secretName: prometheus-tls + hosts: + - prometheus.{{ cluster_name }}.2i2c.cloud + grafana: + grafana.ini: + server: + root_url: https://grafana.{{ cluster_name }}.2i2c.cloud/ + ingress: + hosts: + - grafana.{{ cluster_name }}.2i2c.cloud + tls: + - secretName: grafana-tls + hosts: + - grafana.{{ cluster_name }}.2i2c.cloud + \ No newline at end of file From af9516a557a8529d13ff4e28216194a246e11717 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Tue, 22 Nov 2022 10:36:06 +0200 Subject: [PATCH 031/105] Remove the template configs from various validations --- .github/workflows/validate-clusters.yaml | 4 ++++ .pre-commit-config.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/validate-clusters.yaml b/.github/workflows/validate-clusters.yaml index 5ecb07b859..c279bd515b 100644 --- a/.github/workflows/validate-clusters.yaml +++ b/.github/workflows/validate-clusters.yaml @@ -18,6 +18,8 @@ on: - deployer/** - requirements.txt - .github/workflows/validate-clusters.yaml + paths-ignore: + - config/clusters/templates push: branches: - master @@ -27,6 +29,8 @@ on: - deployer/** - requirements.txt - .github/workflows/validate-clusters.yaml + paths-ignore: + - config/clusters/templates tags: - "**" workflow_dispatch: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 284fb8c4a7..228c617c54 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -50,4 +50,4 @@ repos: hooks: - id: sops-encryption # Add files here if they contain the word 'secret' but should not be encrypted - exclude: secrets\.md|helm-charts/support/templates/prometheus-ingres-auth/secret\.yaml|helm-charts/basehub/templates/dex/secret\.yaml|helm-charts/basehub/templates/static/secret\.yaml + exclude: secrets\.md|helm-charts/support/templates/prometheus-ingres-auth/secret\.yaml|helm-charts/basehub/templates/dex/secret\.yaml|helm-charts/basehub/templates/static/secret\.yaml|config/clusters/templates/gcp/support\.secret\.values\.yaml From e867c07afd1a372fbcdf95c41917993523ae51d4 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Tue, 22 Nov 2022 10:51:38 +0200 Subject: [PATCH 032/105] Exclude templates dir frm upgrade decission also --- deployer/helm_upgrade_decision.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/deployer/helm_upgrade_decision.py b/deployer/helm_upgrade_decision.py index c37aa4307b..d79a9d3848 100644 --- a/deployer/helm_upgrade_decision.py +++ b/deployer/helm_upgrade_decision.py @@ -91,7 +91,11 @@ def get_all_cluster_yaml_files(is_test=False): return set(root_path.glob("tests/test-clusters/**/cluster.yaml")) # We are NOT running a test via pytest. We only care about the clusters under config/clusters - return set(root_path.glob("config/clusters/**/cluster.yaml")) + return { + path + for path in root_path.glob("config/clusters/**/cluster.yaml") + if "templates" not in path.as_posix() + } def generate_hub_matrix_jobs( From 9d1ff859cea15f50cfd3b47f23363fe8e80caae7 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Tue, 22 Nov 2022 11:34:41 +0200 Subject: [PATCH 033/105] Move the generators to separate dir --- deployer/__main__.py | 4 ++-- deployer/generate/__init__.py | 0 deployer/{ => generate}/generate_aws_cluster.py | 2 +- deployer/{ => generate}/generate_gcp_cluster.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 deployer/generate/__init__.py rename deployer/{ => generate}/generate_aws_cluster.py (98%) rename deployer/{ => generate}/generate_gcp_cluster.py (98%) diff --git a/deployer/__main__.py b/deployer/__main__.py index e543b05352..6bed4c9145 100644 --- a/deployer/__main__.py +++ b/deployer/__main__.py @@ -3,8 +3,8 @@ import deployer.central_grafana # noqa: F401 import deployer.debug # noqa: F401 import deployer.deployer # noqa: F401 -import deployer.generate_aws_cluster # noqa: F401 -import deployer.generate_gcp_cluster # noqa: F401 +import deployer.generate.generate_aws_cluster # noqa: F401 +import deployer.generate.generate_gcp_cluster # noqa: F401 from .cli_app import app diff --git a/deployer/generate/__init__.py b/deployer/generate/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/deployer/generate_aws_cluster.py b/deployer/generate/generate_aws_cluster.py similarity index 98% rename from deployer/generate_aws_cluster.py rename to deployer/generate/generate_aws_cluster.py index 8c525d4f76..7b283e1603 100644 --- a/deployer/generate_aws_cluster.py +++ b/deployer/generate/generate_aws_cluster.py @@ -5,7 +5,7 @@ import jinja2 import typer -from .cli_app import app +from deployer.cli_app import app REPO_ROOT = Path(__file__).parent.parent diff --git a/deployer/generate_gcp_cluster.py b/deployer/generate/generate_gcp_cluster.py similarity index 98% rename from deployer/generate_gcp_cluster.py rename to deployer/generate/generate_gcp_cluster.py index 304475b473..07c3c5a6f6 100644 --- a/deployer/generate_gcp_cluster.py +++ b/deployer/generate/generate_gcp_cluster.py @@ -7,8 +7,8 @@ import jinja2 import typer -from .cli_app import app -from .utils import print_colour +from deployer.cli_app import app +from deployer.utils import print_colour REPO_ROOT = Path(__file__).parent.parent From 73667c41ff47fbb9f2262bf6bcd7a87ed4aaf37d Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Wed, 23 Nov 2022 10:14:28 +0200 Subject: [PATCH 034/105] Use relative imports and update repo root path --- deployer/generate/generate_aws_cluster.py | 4 ++-- deployer/generate/generate_gcp_cluster.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/deployer/generate/generate_aws_cluster.py b/deployer/generate/generate_aws_cluster.py index 7b283e1603..14e8f81652 100644 --- a/deployer/generate/generate_aws_cluster.py +++ b/deployer/generate/generate_aws_cluster.py @@ -5,9 +5,9 @@ import jinja2 import typer -from deployer.cli_app import app +from ..cli_app import app -REPO_ROOT = Path(__file__).parent.parent +REPO_ROOT = Path(__file__).parent.parent.parent def aws(cluster_name, hub_type, cluster_region): diff --git a/deployer/generate/generate_gcp_cluster.py b/deployer/generate/generate_gcp_cluster.py index 07c3c5a6f6..d583e82150 100644 --- a/deployer/generate/generate_gcp_cluster.py +++ b/deployer/generate/generate_gcp_cluster.py @@ -7,10 +7,10 @@ import jinja2 import typer -from deployer.cli_app import app -from deployer.utils import print_colour +from ..cli_app import app +from ..utils import print_colour -REPO_ROOT = Path(__file__).parent.parent +REPO_ROOT = Path(__file__).parent.parent.parent def generate_terraform_file(cluster_name, cluster_region, project_id, hub_type): From 3187b49dbae9f268190e9a4b2b1e2a3bb065bb00 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Wed, 23 Nov 2022 10:42:29 +0200 Subject: [PATCH 035/105] Use path negation instead --- .github/workflows/validate-clusters.yaml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/validate-clusters.yaml b/.github/workflows/validate-clusters.yaml index c279bd515b..1348f17b8a 100644 --- a/.github/workflows/validate-clusters.yaml +++ b/.github/workflows/validate-clusters.yaml @@ -14,23 +14,21 @@ on: - master paths: - config/clusters/** + - !config/clusters/templates - helm-charts/** - deployer/** - requirements.txt - .github/workflows/validate-clusters.yaml - paths-ignore: - - config/clusters/templates push: branches: - master paths: - config/clusters/** + - !config/clusters/templates - helm-charts/** - deployer/** - requirements.txt - .github/workflows/validate-clusters.yaml - paths-ignore: - - config/clusters/templates tags: - "**" workflow_dispatch: From d556beba7edd51e4211b355d7164b71b3609b151 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Wed, 23 Nov 2022 10:46:28 +0200 Subject: [PATCH 036/105] Quote everything --- .github/workflows/validate-clusters.yaml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/validate-clusters.yaml b/.github/workflows/validate-clusters.yaml index 1348f17b8a..a6d3bc2351 100644 --- a/.github/workflows/validate-clusters.yaml +++ b/.github/workflows/validate-clusters.yaml @@ -13,22 +13,22 @@ on: branches: - master paths: - - config/clusters/** - - !config/clusters/templates - - helm-charts/** - - deployer/** - - requirements.txt - - .github/workflows/validate-clusters.yaml + - "config/clusters/**" + - "!config/clusters/templates" + - "helm-charts/**" + - "deployer/**" + - "requirements.txt" + - ".github/workflows/validate-clusters.yaml" push: branches: - master paths: - - config/clusters/** - - !config/clusters/templates - - helm-charts/** - - deployer/** - - requirements.txt - - .github/workflows/validate-clusters.yaml + - "config/clusters/**" + - "!config/clusters/templates" + - "helm-charts/**" + - "deployer/**" + - "requirements.txt" + - ".github/workflows/validate-clusters.yaml" tags: - "**" workflow_dispatch: From 0b9cdbff50da8d3f8274f6e6db9cb3360fec1ee5 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Wed, 23 Nov 2022 10:54:04 +0200 Subject: [PATCH 037/105] Exclude templates from list of clusters --- .github/workflows/validate-clusters.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/validate-clusters.yaml b/.github/workflows/validate-clusters.yaml index a6d3bc2351..5b230ab8ed 100644 --- a/.github/workflows/validate-clusters.yaml +++ b/.github/workflows/validate-clusters.yaml @@ -112,7 +112,8 @@ jobs: # Construct a matrix of all clusters matrix = [] for cluster in cluster_folders: - matrix.append({"cluster_name": cluster}) + if cluster != "templates": + matrix.append({"cluster_name": cluster}) # Write matrix to the GITHUB_ENV file in GitHub Actions env_file = os.getenv("GITHUB_ENV") From c7705c71305ca97c16dcc4211a475fc0005048d5 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Wed, 23 Nov 2022 15:15:09 +0200 Subject: [PATCH 038/105] More comments --- deployer/generate/generate_gcp_cluster.py | 27 +++++++++++++++++------ 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/deployer/generate/generate_gcp_cluster.py b/deployer/generate/generate_gcp_cluster.py index d583e82150..6c0c3f5772 100644 --- a/deployer/generate/generate_gcp_cluster.py +++ b/deployer/generate/generate_gcp_cluster.py @@ -15,10 +15,9 @@ def generate_terraform_file(cluster_name, cluster_region, project_id, hub_type): """ - Generates the cluster_name.tfvars terraform file + Generates the `terraform/gcp/projects/.tfvars` terraform file required to create a GCP cluster """ - with open(REPO_ROOT / f"terraform/gcp/projects/{hub_type}-template.tfvars") as f: tfvars_template = jinja2.Template(f.read()) @@ -37,6 +36,9 @@ def generate_terraform_file(cluster_name, cluster_region, project_id, hub_type): def generate_cluster_config_file(cluster_config_directory, vars): + """ + Generates the `config//cluster.yaml` config + """ with open(REPO_ROOT / "config/clusters/templates/gcp/cluster.yaml") as f: cluster_yaml_template = jinja2.Template(f.read()) with open(cluster_config_directory / "cluster.yaml", "w") as f: @@ -44,10 +46,21 @@ def generate_cluster_config_file(cluster_config_directory, vars): def generate_support_files(cluster_config_directory, vars): + """ + Generates files related to support components. + + They are required to deploy the support chart for the cluster + and to configure the Prometheus instance. + + Generates: + - `config//support.values.yaml` + - `config//enc-support.secret.values.yaml` + """ # Generate the suppport values file `support.values.yaml` print_colour("Generating the support values file...", "yellow") with open(REPO_ROOT / "config/clusters/templates/gcp/support.values.yaml") as f: support_values_yaml_template = jinja2.Template(f.read()) + with open(cluster_config_directory / "support.values.yaml", "w") as f: f.write(support_values_yaml_template.render(**vars)) print_colour(f"{cluster_config_directory}/support.values.yaml created") @@ -84,12 +97,12 @@ def generate_config_directory( cluster_name, cluster_region, project_id, hub_type, hub_name ): """ - Generates the required `config` directory and files for hubs on a GCP cluster + Generates the required `config` directory for hubs on a GCP cluster - Generates the following files, in the directory: - - cluster.yaml file - - support.values.yaml file - - enc-support.secret.values.yaml file + Generates the following files: + - `config//cluster.yaml` + - `config//support.values.yaml` + - `config//enc-support.secret.values.yaml` """ cluster_config_directory = REPO_ROOT / "config/clusters" / cluster_name From d109ed2118c60ec165d8e2b2b467699e0663b851 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Wed, 23 Nov 2022 15:51:39 +0200 Subject: [PATCH 039/105] Update the README --- deployer/README.md | 35 +++++++++++++++++------ deployer/generate/generate_gcp_cluster.py | 1 - 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/deployer/README.md b/deployer/README.md index 6d39752268..1a40a6380e 100644 --- a/deployer/README.md +++ b/deployer/README.md @@ -43,8 +43,8 @@ This section descripts all the deployment related subcommands the `deployer` can │ /home │ │ exec-hub-shell Pop an interactive shell in the hub pod │ │ generate-aws-cluster Automatically generate the files required to setup a new cluster on AWS │ -│ generate-gcp-cluster Automatically generate the terraform config file required to setup a new cluster │ -│ on GCP │ +│ generate-gcp-cluster Automatically generates the default intitial files required to setup a new │ +│ cluster on GCP │ │ generate-helm-upgrade-jobs Analyse added or modified files from a GitHub Pull Request and decide which │ │ clusters and/or hubs require helm upgrades to be performed for their *hub helm │ │ charts or the support helm chart. │ @@ -226,15 +226,32 @@ The files are generated based on the jsonnet templates in: ### `generate-gcp-cluster` -This function generates the terraform config file for a GCP cluster, based on a few input fields, -the name of the cluster, the region where the cluster will be deployed and whether the hub deployed there would need dask or not. +This function generates the infrastructure terraform file and the configuration directory +for a GCP cluster. Generates: - - a .tfvars file - -The terraform config is generated based on the terraform templates in: - - (`terraform/basehub-template.tfvars`)[https://github.com/2i2c-org/infrastructure/blob/master/terraform/gcp/projects/basehub-template.tfvars] - - (`terraform/daskhub-template.tfvars`)[https://github.com/2i2c-org/infrastructure/blob/master/terraform/gcp/projects/daskhub-template.tfvars] + - infrastructure file: + - `terraform/gcp/projects/.tfvars` + - cluster configuration files: + - `config//cluster.yaml` + - `config//support.values.yaml` + - `config//enc-support.secret.values.yaml` + + The files are generated based on the content in a set of template files and a few input fields: + - `cluster_name` - the name of the cluster + - `cluster_region`- the region where the cluster will be deployed + - `project_id` - the project ID of the GCP project + - `hub_type` (basehub/daskhub) - whether the hub deployed there would need dask or not + - `hub_name` - the name of the first hub which will be deployed in the cluster (usually `staging`) + +The templates have a set of default features and define some opinionated characteristics for the cluster. +These defaults are described in each file template. + + The infrastructure terraform config is generated based on the terraform templates in: + - (`terraform/basehub-template.tfvars`)[https://github.com/2i2c-org/infrastructure/blob/master/terraform/gcp/projects/basehub-template.tfvars] + - (`terraform/daskhub-template.tfvars`)[https://github.com/2i2c-org/infrastructure/blob/master/terraform/gcp/projects/daskhub-template.tfvars] + The cluster configuration directory is generated based on the templates in: + - (`config/clusters/templates/gcp`)[https://github.com/2i2c-org/infrastructure/blob/master/config/clusters/templates/gcp] **Command line usage:** diff --git a/deployer/generate/generate_gcp_cluster.py b/deployer/generate/generate_gcp_cluster.py index 6c0c3f5772..333a379e54 100644 --- a/deployer/generate/generate_gcp_cluster.py +++ b/deployer/generate/generate_gcp_cluster.py @@ -104,7 +104,6 @@ def generate_config_directory( - `config//support.values.yaml` - `config//enc-support.secret.values.yaml` """ - cluster_config_directory = REPO_ROOT / "config/clusters" / cluster_name vars = { From 5717bcc4e2faba40829c1dc2feb0f547afc5e76d Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Wed, 30 Nov 2022 13:22:32 +0200 Subject: [PATCH 040/105] Comment on excluding the cluster template dir from workflows --- .github/workflows/validate-clusters.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/validate-clusters.yaml b/.github/workflows/validate-clusters.yaml index 5b230ab8ed..f210d0c0cf 100644 --- a/.github/workflows/validate-clusters.yaml +++ b/.github/workflows/validate-clusters.yaml @@ -14,6 +14,8 @@ on: - master paths: - "config/clusters/**" + # Exclude changes to the templates directory from + # triggering this workflow - "!config/clusters/templates" - "helm-charts/**" - "deployer/**" @@ -24,6 +26,8 @@ on: - master paths: - "config/clusters/**" + # Exclude changes the templates directory from + # triggering this workflow - "!config/clusters/templates" - "helm-charts/**" - "deployer/**" @@ -112,6 +116,10 @@ jobs: # Construct a matrix of all clusters matrix = [] for cluster in cluster_folders: + # The `templates` directory contains template yaml configs + # and doesn't represent a "real" cluster. + # This is why we need to exclude it from the list of clusters + # and hence all workflows, otherwise it will cause them to fail. if cluster != "templates": matrix.append({"cluster_name": cluster}) From 6f8f226abca0ec3b0d88fdcfede6a7c4c34e5425 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Wed, 30 Nov 2022 13:23:36 +0200 Subject: [PATCH 041/105] Remove comment about enabling auth0 for auth --- config/clusters/templates/gcp/cluster.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/config/clusters/templates/gcp/cluster.yaml b/config/clusters/templates/gcp/cluster.yaml index 33040d7aac..8c51636f46 100644 --- a/config/clusters/templates/gcp/cluster.yaml +++ b/config/clusters/templates/gcp/cluster.yaml @@ -17,10 +17,6 @@ hubs: domain: {{ hub_name }}.{{ cluster_name }}.2i2c.cloud helm_chart: {{ hub_type }} auth0: - # Tip: Uncomment the line below and enable auth0 - # if you want to use it for this hub. - # By default, it is disabled - # connection: github/google enabled: false helm_chart_values_files: - common.values.yaml From 460712b060fa37398412617126b19876afea1214 Mon Sep 17 00:00:00 2001 From: Georgiana Date: Wed, 30 Nov 2022 13:24:23 +0200 Subject: [PATCH 042/105] Add special chars for the prometheus passw Co-authored-by: Yuvi Panda --- deployer/generate/generate_gcp_cluster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployer/generate/generate_gcp_cluster.py b/deployer/generate/generate_gcp_cluster.py index 333a379e54..6b2c57e46e 100644 --- a/deployer/generate/generate_gcp_cluster.py +++ b/deployer/generate/generate_gcp_cluster.py @@ -67,7 +67,7 @@ def generate_support_files(cluster_config_directory, vars): # Generate and encrypt prometheus credentials into `enc-support.secret.values.yaml` print_colour("Generating the prometheus credentials encrypted file...", "yellow") - alphabet = string.ascii_letters + string.digits + alphabet = string.ascii_letters + string.digits + string.punctuation credentials = { "username": "".join(secrets.choice(alphabet) for i in range(64)), "password": "".join(secrets.choice(alphabet) for i in range(64)), From f86ddaa4070e7aafd8d8c11a35286059a957d808 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Wed, 30 Nov 2022 13:29:58 +0200 Subject: [PATCH 043/105] Fix typo --- deployer/generate/generate_gcp_cluster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployer/generate/generate_gcp_cluster.py b/deployer/generate/generate_gcp_cluster.py index 6b2c57e46e..208ce501b4 100644 --- a/deployer/generate/generate_gcp_cluster.py +++ b/deployer/generate/generate_gcp_cluster.py @@ -142,7 +142,7 @@ def generate_gcp_cluster( hub_name: str = typer.Option(..., prompt="Name of the first hub"), ): """ - Automatically generates the default intitial files required to setup a new cluster on GCP + Automatically generates the initial files, required to setup a new cluster on GCP """ # Automatically generate the terraform config file generate_terraform_file(cluster_name, cluster_region, project_id, hub_type) From 07c26ea0b66b7eda4129dc893cbc99370b35302f Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Wed, 30 Nov 2022 13:30:38 +0200 Subject: [PATCH 044/105] Update readme with typo fix --- deployer/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deployer/README.md b/deployer/README.md index 1a40a6380e..0f097b98e5 100644 --- a/deployer/README.md +++ b/deployer/README.md @@ -43,8 +43,8 @@ This section descripts all the deployment related subcommands the `deployer` can │ /home │ │ exec-hub-shell Pop an interactive shell in the hub pod │ │ generate-aws-cluster Automatically generate the files required to setup a new cluster on AWS │ -│ generate-gcp-cluster Automatically generates the default intitial files required to setup a new │ -│ cluster on GCP │ +│ generate-gcp-cluster Automatically generates the initial files, required to setup a new cluster on │ +│ GCP │ │ generate-helm-upgrade-jobs Analyse added or modified files from a GitHub Pull Request and decide which │ │ clusters and/or hubs require helm upgrades to be performed for their *hub helm │ │ charts or the support helm chart. │ From 250228b861ded98c3b1735fc453f9ec4992f6c08 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Fri, 2 Dec 2022 01:59:53 -0800 Subject: [PATCH 045/105] Enable pre-puller + update image on nasa cryo hub --- config/clusters/nasa-cryo/common.values.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/config/clusters/nasa-cryo/common.values.yaml b/config/clusters/nasa-cryo/common.values.yaml index 1de9d35399..76e346105b 100644 --- a/config/clusters/nasa-cryo/common.values.yaml +++ b/config/clusters/nasa-cryo/common.values.yaml @@ -12,6 +12,11 @@ basehub: serverIP: fs-0872256335d483d5f.efs.us-west-2.amazonaws.com baseShareName: / jupyterhub: + prePuller: + continuous: + enabled: true + hook: + enabled: true custom: 2i2c: add_staff_user_ids_to_admin_users: true @@ -66,7 +71,7 @@ basehub: # Image repo: https://github.com/CryoInTheCloud/hub-image image: name: quay.io/cryointhecloud/cryo-hub-image - tag: "3e7ad15ad39d" + tag: "a1bc0061178a" storage: extraVolumeMounts: - name: home From bb0f1a64731d276c66153c9e5fe87049f33c2ac7 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Thu, 1 Dec 2022 14:04:26 +0200 Subject: [PATCH 046/105] Add ucmerced hub config --- config/clusters/2i2c/ucmerced.values.yaml | 41 +++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 config/clusters/2i2c/ucmerced.values.yaml diff --git a/config/clusters/2i2c/ucmerced.values.yaml b/config/clusters/2i2c/ucmerced.values.yaml new file mode 100644 index 0000000000..1b22ca7d5f --- /dev/null +++ b/config/clusters/2i2c/ucmerced.values.yaml @@ -0,0 +1,41 @@ +jupyterhub: + custom: + 2i2c: + add_staff_user_ids_to_admin_users: true + add_staff_user_ids_of_type: "google" + homepage: + templateVars: + org: + name: University of California, Merced + url: http://www.ucmerced.edu/ + logo_url: https://brand.ucmerced.edu/sites/brand.ucmerced.edu/files/images/ucm-logo-text.png + designed_by: + name: 2i2c + url: https://2i2c.org + operated_by: + name: 2i2c + url: https://2i2c.org + funded_by: + name: University of California, Merced + url: http://www.ucmerced.edu/ + hub: + config: + Authenticator: + admin_users: + - schadalapaka@ucmerced.edu + JupyterHub: + authenticator_class: cilogon + CILogonOAuthenticator: + oauth_callback_url: https://ucmerced.2i2c.cloud/hub/oauth_callback + shown_idps: + - urn:mace:incommon:ucmerced.edu + - https://accounts.google.com/o/oauth2/auth + allowed_idps: + urn:mace:incommon:ucmerced.edu: + username_derivation: + username_claim: "eppn" + http://google.com/accounts/o8/id: + username_derivation: + username_claim: "email" + allowed_domains: + - "2i2c.org" From 4fa59efeb5f3f10a65305a09bc40f156a2950822 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Thu, 1 Dec 2022 14:16:17 +0200 Subject: [PATCH 047/105] Add ucmerced to the list of 2i2c hubs --- config/clusters/2i2c/cluster.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/config/clusters/2i2c/cluster.yaml b/config/clusters/2i2c/cluster.yaml index 2673f64fff..0fe750e950 100644 --- a/config/clusters/2i2c/cluster.yaml +++ b/config/clusters/2i2c/cluster.yaml @@ -155,3 +155,12 @@ hubs: helm_chart_values_files: - temple.values.yaml - enc-temple.secret.values.yaml + - name: ucmerced + display_name: "UC Merced" + domain: ucmerced.2i2c.cloud + helm_chart: basehub + auth0: + enabled: false + helm_chart_values_files: + - ucmerced.values.yaml + - enc-ucmerced.secret.values.yaml From 766a7507365d986492dc9cfe4b00e2865a79f274 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Thu, 1 Dec 2022 14:18:17 +0200 Subject: [PATCH 048/105] Add cilogon creds for ucmerced --- .../2i2c/enc-ucmerced.secret.values.yaml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 config/clusters/2i2c/enc-ucmerced.secret.values.yaml diff --git a/config/clusters/2i2c/enc-ucmerced.secret.values.yaml b/config/clusters/2i2c/enc-ucmerced.secret.values.yaml new file mode 100644 index 0000000000..d15e4de56d --- /dev/null +++ b/config/clusters/2i2c/enc-ucmerced.secret.values.yaml @@ -0,0 +1,20 @@ +jupyterhub: + hub: + config: + CILogonOAuthenticator: + client_id: ENC[AES256_GCM,data:p24HZ/+OnLepZ6zFmFtCK+J0b5Xk8DPrzxu4JYl3pIL8ar6uZG2xjpABEBPdS4Ug+do+,iv:Sf75vnk9J7T3pQtaGCxh7omzHTK3jGh3uQiU1a6IF+g=,tag:hdU0RTZZlgGy/Z/fFj+jnw==,type:str] + client_secret: ENC[AES256_GCM,data:0JGJfipVvxId+n0iozhNsW+3lvnIjG7qEVd1ogCdQ6u0wisaYBXL0NQK6mz7jdR4tTyCXp3UzdxF5oquq5sL4s9ZGsVdlkDKzdqR3+bwbbvqnfLAHAY=,iv:3uKUTvVhfSuOkvKYl6Qub5QEH/mZ1QR2jOGfz9o/kYk=,tag:lvrNRZN8gKDy+wvjaPvl/A==,type:str] +sops: + kms: [] + gcp_kms: + - resource_id: projects/two-eye-two-see/locations/global/keyRings/sops-keys/cryptoKeys/similar-hubs + created_at: "2022-12-01T12:17:47Z" + enc: CiUA4OM7eOy8gcW/5Y89U0ioJkHkU0/Nj6PHf3hS8Mmoc27gsAYMEkkA+0T9hYZTjME7S8PZ8gELY2j7N0ZW+sFGsBX3IP8bLJB7hi/N2FZmF1wMDKsxYYeHVe69fN5B2tZ9i6giuDwbStA5FsVM2dd8 + azure_kv: [] + hc_vault: [] + age: [] + lastmodified: "2022-12-01T12:17:48Z" + mac: ENC[AES256_GCM,data:RvhV7FzB9hYoo/DNBcfCjh3xNg8jcP/r3s+MyaMqYHct8LnbhzzprJTb0AD4vGd9gWxwDazVwfVSVYMIdgKa6704p/D/l0gPd3ukRz3GERP7HSnzzVlJyWf6odwAKVihIgjU1C7e6Qp4N9YwUSOjethkImjRvZOhvbdOGpUYryg=,iv:5rHAm9IPbjHgzZoZrJSJKgH5JRFYSBF3oKT4Vpps+OA=,tag:bLKy36xlLzcFV3e9lMflGA==,type:str] + pgp: [] + unencrypted_suffix: _unencrypted + version: 3.7.3 From 31ff664af35f932f5a7cbd4978a5ad34325aebb8 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Mon, 5 Dec 2022 11:45:37 -0800 Subject: [PATCH 049/105] Use public repo for private auth demo Ref https://github.com/2i2c-org/infrastructure/issues/1975 --- config/clusters/2i2c/staging.values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/clusters/2i2c/staging.values.yaml b/config/clusters/2i2c/staging.values.yaml index 81c011865c..ae3e19ed5b 100644 --- a/config/clusters/2i2c/staging.values.yaml +++ b/config/clusters/2i2c/staging.values.yaml @@ -6,7 +6,7 @@ staticWebsite: enabled: true source: git: - repo: https://github.com/yuvipanda/test-repo-push + repo: https://github.com/2i2c-org/docs branch: master ingress: host: staging.2i2c.cloud From ff63ef66cff69fd43578730774c1c8c8ce879bb1 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Tue, 6 Dec 2022 11:07:00 +0200 Subject: [PATCH 050/105] Remove the templates directory from the list of hubs --- docs/scripts/render_hubs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/scripts/render_hubs.py b/docs/scripts/render_hubs.py index 9b007ac6f7..a51ff1fbe5 100644 --- a/docs/scripts/render_hubs.py +++ b/docs/scripts/render_hubs.py @@ -13,7 +13,7 @@ clusters = [ filepath for filepath in path_clusters.glob("**/*cluster.yaml") - if "tests/" not in str(filepath) + if "tests/" not in str(filepath) and "templates" not in str(filepath) ] hub_list = [] From 60bdcfbfb7650efafa1d72f2ffe1b9bf9eb9e504 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Tue, 6 Dec 2022 12:25:57 +0200 Subject: [PATCH 051/105] Fix up docs repo branch name --- config/clusters/2i2c/staging.values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/clusters/2i2c/staging.values.yaml b/config/clusters/2i2c/staging.values.yaml index ae3e19ed5b..8feaba8929 100644 --- a/config/clusters/2i2c/staging.values.yaml +++ b/config/clusters/2i2c/staging.values.yaml @@ -7,7 +7,7 @@ staticWebsite: source: git: repo: https://github.com/2i2c-org/docs - branch: master + branch: main ingress: host: staging.2i2c.cloud path: /textbook From 6519aad36792b3bdb3923c146b076165556bc06e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Dec 2022 20:42:58 +0000 Subject: [PATCH 052/105] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.2.2 → v3.3.0](https://github.com/asottile/pyupgrade/compare/v3.2.2...v3.3.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 228c617c54..994631524d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,7 +20,7 @@ repos: # Autoformat: Python code, syntax patterns are modernized - repo: https://github.com/asottile/pyupgrade - rev: v3.2.2 + rev: v3.3.0 hooks: - id: pyupgrade args: From 5547ef72e7a19a40a655d5efc14401682a04083d Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Mon, 5 Dec 2022 16:38:11 +0000 Subject: [PATCH 053/105] Add section asking how to manage users --- .github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml b/.github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml index aaeeafff17..24079d9b01 100644 --- a/.github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml +++ b/.github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml @@ -61,6 +61,15 @@ body: validations: required: true + - type: dropdown + label: "[GitHub Auth only] How would you like to manage your users?" + description: | + Please describe how you would prefer to manage your users accessing the hub via GitHub Auth. + options: + - Manually, by adding specific GitHub handles in the JupyterHub Admin panel + - Allowing members of a specific GitHub organization + - Allowing members of specific GitHub team(s) + - type: markdown attributes: value: | From 2de2ef498fac509e9a3dbc5df3dcddf09ee208da Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Mon, 5 Dec 2022 17:14:09 +0000 Subject: [PATCH 054/105] Add missing attributes key, add link to JHub admin docs --- .github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml b/.github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml index 24079d9b01..dee3a864e6 100644 --- a/.github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml +++ b/.github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml @@ -62,13 +62,14 @@ body: required: true - type: dropdown - label: "[GitHub Auth only] How would you like to manage your users?" - description: | - Please describe how you would prefer to manage your users accessing the hub via GitHub Auth. - options: - - Manually, by adding specific GitHub handles in the JupyterHub Admin panel - - Allowing members of a specific GitHub organization - - Allowing members of specific GitHub team(s) + attributes: + label: "[GitHub Auth only] How would you like to manage your users?" + description: | + Please describe how you would prefer to manage your users accessing the hub via GitHub Auth. + options: + - Manually, by adding specific GitHub handles in the [JupyterHub Admin panel](https://docs.2i2c.org/en/latest/admin/howto/manage-users.html#manage-users-from-the-administrator-panel) + - Allowing members of a specific GitHub organization + - Allowing members of specific GitHub team(s) - type: markdown attributes: From b4c29745986f3639c2323ced30b1cf1228a8dac6 Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Mon, 5 Dec 2022 17:16:06 +0000 Subject: [PATCH 055/105] Ask about restricting profiles to teams --- .github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml b/.github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml index dee3a864e6..eed5bcbbc5 100644 --- a/.github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml +++ b/.github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml @@ -71,6 +71,16 @@ body: - Allowing members of a specific GitHub organization - Allowing members of specific GitHub team(s) + - type: textarea + attributes: + label: "[GitHub Teams Auth only] Profile restriction based on team membership" + description: | + If you wish to offer a range of machine sizes/image types but to only a subset of your users, we can facilitate this through GitHub Teams. + Please provide a list of GitHub Teams in your org and what resources you'd like each to access. + placeholder: | + @MyCoolOrg/all-users: Small and Medium sized machines + @MyCoolOrg/advanced-users: Small, Medium, Large and GPU machines + - type: markdown attributes: value: | From db1a6c4818501a36f137230f5ccca2c65c7011a1 Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Mon, 5 Dec 2022 17:19:02 +0000 Subject: [PATCH 056/105] Add extra step to add eng to org to setup GitHub auth --- .github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml b/.github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml index eed5bcbbc5..698f742b48 100644 --- a/.github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml +++ b/.github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml @@ -209,7 +209,8 @@ body: options: - label: 1. Deploy information filled in above - label: 2. Engineer who will deploy the hub is assigned - - label: 3. Initial Hub deployment PR - - label: 4. Administrators able to log on -> Hub now in steady-state + - label: 3. If using GitHub Orgs/Teams Auth, Engineer is given Owner rights to the org to set this up. + - label: 4. Initial Hub deployment PR + - label: 5. Administrators able to log on -> Hub now in steady-state validations: required: false From c912d7d72f999b29dac4cae25f2a2392330eb99e Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Mon, 5 Dec 2022 17:20:35 +0000 Subject: [PATCH 057/105] Remove link from dropdown option It doesn't render properly in the form --- .github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml b/.github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml index 698f742b48..b790294a43 100644 --- a/.github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml +++ b/.github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml @@ -67,7 +67,7 @@ body: description: | Please describe how you would prefer to manage your users accessing the hub via GitHub Auth. options: - - Manually, by adding specific GitHub handles in the [JupyterHub Admin panel](https://docs.2i2c.org/en/latest/admin/howto/manage-users.html#manage-users-from-the-administrator-panel) + - Manually, by adding specific GitHub handles in the JupyterHub Admin panel - Allowing members of a specific GitHub organization - Allowing members of specific GitHub team(s) From 7ff7b09a9e526f770533fd5ee97598fb147e8533 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Dec 2022 17:32:21 +0000 Subject: [PATCH 058/105] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml b/.github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml index b790294a43..31c06fa2ef 100644 --- a/.github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml +++ b/.github/ISSUE_TEMPLATE/2_new-hub-provide-info.yml @@ -62,7 +62,7 @@ body: required: true - type: dropdown - attributes: + attributes: label: "[GitHub Auth only] How would you like to manage your users?" description: | Please describe how you would prefer to manage your users accessing the hub via GitHub Auth. @@ -72,7 +72,7 @@ body: - Allowing members of specific GitHub team(s) - type: textarea - attributes: + attributes: label: "[GitHub Teams Auth only] Profile restriction based on team membership" description: | If you wish to offer a range of machine sizes/image types but to only a subset of your users, we can facilitate this through GitHub Teams. From 23b8f02a08de3f4ae5136355d0d8224af3fdd5bb Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Tue, 6 Dec 2022 14:39:46 +0200 Subject: [PATCH 059/105] Update repo again --- config/clusters/2i2c/staging.values.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/clusters/2i2c/staging.values.yaml b/config/clusters/2i2c/staging.values.yaml index 8feaba8929..b41a05a1cf 100644 --- a/config/clusters/2i2c/staging.values.yaml +++ b/config/clusters/2i2c/staging.values.yaml @@ -6,8 +6,8 @@ staticWebsite: enabled: true source: git: - repo: https://github.com/2i2c-org/docs - branch: main + repo: https://github.com/jupyter/try.jupyter.org + branch: gh-pages ingress: host: staging.2i2c.cloud path: /textbook From 9e7137f983e43446402dcfb0128c3f99bc8bcbc9 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Mon, 5 Dec 2022 18:24:15 -0800 Subject: [PATCH 060/105] Explicitly allow requstor-pays access to usgs-landsat Without this, only the s3 buckets we explicitly create (scratch) can be accessed by the users on the hub. Ideally, we want to allow access to: 1. Allow-listed buckets inside the AWS account hub is in, 2. *All* buckets outside the AWS account, based on access control ( including requstor pays) on the external bucket itself. Unsure quite yet how to do this, we allow-list the external buckets too for now. This doesn't scale and we should fix this up! Thanks to @scottyhq for helping debug --- terraform/aws/projects/nasa-cryo.tfvars | 65 +++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/terraform/aws/projects/nasa-cryo.tfvars b/terraform/aws/projects/nasa-cryo.tfvars index b7aa0c6639..6efda3294c 100644 --- a/terraform/aws/projects/nasa-cryo.tfvars +++ b/terraform/aws/projects/nasa-cryo.tfvars @@ -13,16 +13,75 @@ user_buckets = { }, } - hub_cloud_permissions = { "staging" : { requestor_pays: true, bucket_admin_access: ["scratch-staging"], - extra_iam_policy: "" + # Provides readonly requestor-pays access to usgs-landsat bucket + # FIXME: We should find a way to allow access to *all* requestor pays + # buckets, without having to explicitly list them. However, we don't want + # to give access to all *internal* s3 buckets willy-nilly - this can be + # a massive security hole, especially if terraform state is also here. + # As a temporary measure, we allow-list buckets here. Same as uwhackweeks. + extra_iam_policy: <<-EOT + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:*" + ], + "Resource": [ + "arn:aws:s3:::usgs-landsat" + ] + }, + { + "Effect": "Allow", + "Action": [ + "s3:*" + ], + "Resource": [ + "arn:aws:s3:::usgs-landsat/*" + ] + } + ] + } + EOT }, "prod" : { requestor_pays: true, bucket_admin_access: ["scratch"], - extra_iam_policy: "" + # Provides readonly requestor-pays access to usgs-landsat bucket + # FIXME: We should find a way to allow access to *all* requestor pays + # buckets, without having to explicitly list them. However, we don't want + # to give access to all *internal* s3 buckets willy-nilly - this can be + # a massive security hole, especially if terraform state is also here. + # As a temporary measure, we allow-list buckets here. Same as uwhackweeks. + extra_iam_policy: <<-EOT + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:*" + ], + "Resource": [ + "arn:aws:s3:::usgs-landsat" + ] + }, + { + "Effect": "Allow", + "Action": [ + "s3:*" + ], + "Resource": [ + "arn:aws:s3:::usgs-landsat/*" + ] + } + ] + } + EOT }, } From cfd3dde0edc9800448ea5eb0660a16b8fa520294 Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Fri, 9 Dec 2022 14:35:28 +0000 Subject: [PATCH 061/105] fix typo --- docs/howto/features/gpu.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/howto/features/gpu.md b/docs/howto/features/gpu.md index 48d6fd52b4..9e49165137 100644 --- a/docs/howto/features/gpu.md +++ b/docs/howto/features/gpu.md @@ -14,7 +14,7 @@ On AWS, GPUs are provisioned by using P series nodes. Before they can be accessed, you need to ask AWS for increased quota of P series nodes. -1. Login to the AWS management console of the account the cluster i +1. Login to the AWS management console of the account the cluster is in. 2. Make sure you are in same region the cluster is in, by checking the region selector on the top right. **This is very important**, as getting From f29b3d4df83a147763153dbb8ca60fe1e87d4180 Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Fri, 9 Dec 2022 14:35:56 +0000 Subject: [PATCH 062/105] fix formatting --- docs/howto/features/gpu.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/howto/features/gpu.md b/docs/howto/features/gpu.md index 9e49165137..0603b7bfe0 100644 --- a/docs/howto/features/gpu.md +++ b/docs/howto/features/gpu.md @@ -44,7 +44,6 @@ AWS, and we can configure a node group there to provide us GPUs. 1. In the `notebookNodes` definition in the appropriate `.jsonnet` file, add a node definition for the appropriate GPU node type: - ``` { instanceType: "g4dn.xlarge", @@ -127,7 +126,8 @@ jupyterhub: **Do not** use the `latest` or `master` tags - find a specific tag listed for the image you want, and use that. ``` -3. The [NVIDIA_DRIVER_CAPABILITIES](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/user-guide.html#driver-capabilities) + +3. The [`NVIDIA_DRIVER_CAPABILITIES`](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/user-guide.html#driver-capabilities) environment variable tells the GPU driver what kind of libraries and tools to inject into the container. Without setting this, GPUs can not be accessed. @@ -159,6 +159,7 @@ this works! ``` If on an image with pytorch instead, try this: + ```python import torch @@ -169,4 +170,4 @@ this works! 4. Remember to explicitly shut down your server after testing, as GPU instances can get expensive! -If either of those tests fail, something is wrong and off you go debugging :) \ No newline at end of file +If either of those tests fail, something is wrong and off you go debugging :) From a5977eec3b2aa7ab1ac1cb014fe118ca862079bf Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Fri, 9 Dec 2022 14:36:16 +0000 Subject: [PATCH 063/105] fix recommended gpu profilelist config --- docs/howto/features/gpu.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/howto/features/gpu.md b/docs/howto/features/gpu.md index 0603b7bfe0..d79dc4656f 100644 --- a/docs/howto/features/gpu.md +++ b/docs/howto/features/gpu.md @@ -81,16 +81,15 @@ a profile. This should be placed in the hub configuration: ```yaml jupyterhub: - extraEnv: - # Temporarily set for *all* pods, including pods without any GPUs, - # to work around https://github.com/2i2c-org/infrastructure/issues/1530 - NVIDIA_DRIVER_CAPABILITIES: compute,utility singleuser: + extraEnv: + # Temporarily set for *all* pods, including pods without any GPUs, + # to work around https://github.com/2i2c-org/infrastructure/issues/1530 + NVIDIA_DRIVER_CAPABILITIES: compute,utility profileList: - display_name: Large + GPU description: 14GB RAM, 4 CPUs, T4 GPU profile_options: - gpu: image: display_name: Image choices: From c08aafa31475002773be39f3a6a64e75af86ac1e Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Mon, 12 Dec 2022 11:15:00 +0100 Subject: [PATCH 064/105] deployer: fix print_colours import statement --- deployer/helm_upgrade_decision.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/deployer/helm_upgrade_decision.py b/deployer/helm_upgrade_decision.py index d79a9d3848..b5deb57d71 100644 --- a/deployer/helm_upgrade_decision.py +++ b/deployer/helm_upgrade_decision.py @@ -10,15 +10,7 @@ from rich.table import Table from ruamel.yaml import YAML -# This try/except block is here because pytest wants to import print_colour from -# deployer.utils, whereas the deployer wants to call it directly from utils. There is no -# way to fix this for one without breaking it for the other until we make the deployer -# an actual pip installable package. See the below issue for discussion on this topic: -# https://github.com/2i2c-org/infrastructure/issues/970 -try: - from utils import print_colour -except ModuleNotFoundError: - pass +from .utils import print_colour yaml = YAML(typ="safe", pure=True) From e29def9de419a700caef664e3569ce159595fbf1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Dec 2022 01:30:08 +0000 Subject: [PATCH 065/105] Bump azure/setup-kubectl in /.github/actions/setup-deploy Bumps [azure/setup-kubectl](https://github.com/azure/setup-kubectl) from 3.0 to 3.1. - [Release notes](https://github.com/azure/setup-kubectl/releases) - [Commits](https://github.com/azure/setup-kubectl/compare/v3.0...v3.1) --- updated-dependencies: - dependency-name: azure/setup-kubectl dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/actions/setup-deploy/action.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup-deploy/action.yaml b/.github/actions/setup-deploy/action.yaml index ecac6fd063..fe47c91ce5 100644 --- a/.github/actions/setup-deploy/action.yaml +++ b/.github/actions/setup-deploy/action.yaml @@ -78,7 +78,7 @@ runs: # Pin kubectl version to 1.23 otherwise interactions with k8s clusters versioned <=1.21 won't work. # See https://github.com/2i2c-org/infrastructure/issues/1271. - - uses: azure/setup-kubectl@v3.0 + - uses: azure/setup-kubectl@v3.1 with: version: "v1.23.5" From ac115a5ce3238598c7aec2a1a5bf9466c4c0581d Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Mon, 12 Dec 2022 09:38:09 -0800 Subject: [PATCH 066/105] Set utoronto staging hub limits to match exam limits Helps the instructor test that resource limits are ok Ref https://github.com/2i2c-org/infrastructure/issues/1905 --- config/clusters/utoronto/staging.values.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/config/clusters/utoronto/staging.values.yaml b/config/clusters/utoronto/staging.values.yaml index c14a353af1..7bb2413f98 100644 --- a/config/clusters/utoronto/staging.values.yaml +++ b/config/clusters/utoronto/staging.values.yaml @@ -1,4 +1,13 @@ jupyterhub: + # superseeded by https://github.com/2i2c-org/infrastructure/pull/1962/ + singleuser: + memory: + # Ensure all students get equal resources during the exam + limit: 2G + guarantee: 2G + cpu: + limit: 1 + guarantee: 1 hub: config: AzureAdOAuthenticator: From 4f854d20f7f69a5be2eb00b5d4df7f5c23d7a39d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 12 Dec 2022 21:21:04 +0000 Subject: [PATCH 067/105] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.3.0 → v3.3.1](https://github.com/asottile/pyupgrade/compare/v3.3.0...v3.3.1) - [github.com/pycqa/isort: 5.10.1 → 5.11.0](https://github.com/pycqa/isort/compare/5.10.1...5.11.0) - [github.com/psf/black: 22.10.0 → 22.12.0](https://github.com/psf/black/compare/22.10.0...22.12.0) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 994631524d..1ce127be20 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,7 +20,7 @@ repos: # Autoformat: Python code, syntax patterns are modernized - repo: https://github.com/asottile/pyupgrade - rev: v3.3.0 + rev: v3.3.1 hooks: - id: pyupgrade args: @@ -28,13 +28,13 @@ repos: # Autoformat: Python code - repo: https://github.com/pycqa/isort - rev: "5.10.1" + rev: "5.11.0" hooks: - id: isort # Autoformat: Python code - repo: https://github.com/psf/black - rev: "22.10.0" + rev: "22.12.0" hooks: - id: black From 4b55082a74316ff85bba7ceab6d7f41ddcfa33ef Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Mon, 12 Dec 2022 21:44:23 -0800 Subject: [PATCH 068/105] Setup new R hub for UToronto Adds a README too with some more info on how this specific cluster of hubs is configured. Ref https://github.com/2i2c-org/infrastructure/issues/1961 --- config/clusters/utoronto/README.md | 32 ++++++++++++++++ config/clusters/utoronto/cluster.yaml | 38 ++++++++++++++----- config/clusters/utoronto/common.values.yaml | 9 +---- .../utoronto/default-common.values.yaml | 16 ++++++++ ...d.values.yaml => default-prod.values.yaml} | 0 ...alues.yaml => default-staging.values.yaml} | 0 ...ml => enc-default-prod.secret.values.yaml} | 0 ...=> enc-default-staging.secret.values.yaml} | 0 .../utoronto/enc-r-prod.secret.values.yaml | 24 ++++++++++++ .../utoronto/enc-r-staging.secret.values.yaml | 24 ++++++++++++ config/clusters/utoronto/r-common.values.yaml | 9 +++++ config/clusters/utoronto/r-prod.values.yaml | 10 +++++ .../clusters/utoronto/r-staging.values.yaml | 6 +++ 13 files changed, 150 insertions(+), 18 deletions(-) create mode 100644 config/clusters/utoronto/README.md create mode 100644 config/clusters/utoronto/default-common.values.yaml rename config/clusters/utoronto/{prod.values.yaml => default-prod.values.yaml} (100%) rename config/clusters/utoronto/{staging.values.yaml => default-staging.values.yaml} (100%) rename config/clusters/utoronto/{enc-prod.secret.values.yaml => enc-default-prod.secret.values.yaml} (100%) rename config/clusters/utoronto/{enc-staging.secret.values.yaml => enc-default-staging.secret.values.yaml} (100%) create mode 100644 config/clusters/utoronto/enc-r-prod.secret.values.yaml create mode 100644 config/clusters/utoronto/enc-r-staging.secret.values.yaml create mode 100644 config/clusters/utoronto/r-common.values.yaml create mode 100644 config/clusters/utoronto/r-prod.values.yaml create mode 100644 config/clusters/utoronto/r-staging.values.yaml diff --git a/config/clusters/utoronto/README.md b/config/clusters/utoronto/README.md new file mode 100644 index 0000000000..22beeb6c98 --- /dev/null +++ b/config/clusters/utoronto/README.md @@ -0,0 +1,32 @@ +# University of Toronto README + +This file documents some of the choices made in the UToronto cluster, +which serves multiple hubs. + +## Staging Hubs + +Each hub gets its own staging hub. They match all configuration, except: + +1. Home directory storage is different, for security isolation +2. Different Login credentials +3. (Possibly) different hub DB sizes, as we still store logs in the hub db dir + (a bad practice we should stop soon). + +## Usernames + +The default hub (at jupyter.utoronto.ca) and its staging hub use an opaque +id (oid) in the form of a [uuid](https://en.wikipedia.org/wiki/Universally_unique_identifier) +as usernames. This caused a bunch of confusion with respect to support, and +hence other hubs use user emails as usernames instead. + +## Config Structure + +For each hub, we want the following files: + +1. `-common.values.yaml` - common values for prod & staging hubs +2. `-staging.values.yaml` - staging config overrides +3. `-prod.values.yaml` - prod config overrides +4. `enc--staging.secret.values.yaml` - `sops` encrypted config for staging +5. `enc--prod.secret.values.yaml` - `sops` encrypted config for prod + +There is also a `common.values.yaml` that is common to *all* the hubs. diff --git a/config/clusters/utoronto/cluster.yaml b/config/clusters/utoronto/cluster.yaml index 797ba57cf1..969350d030 100644 --- a/config/clusters/utoronto/cluster.yaml +++ b/config/clusters/utoronto/cluster.yaml @@ -15,12 +15,10 @@ hubs: auth0: enabled: false helm_chart_values_files: - # The order in which you list files here is the order the will be passed - # to the helm upgrade command in, and that has meaning. Please check - # that you intend for these files to be applied in this order. - common.values.yaml - - staging.values.yaml - - enc-staging.secret.values.yaml + - default-common.values.yaml + - default-staging.values.yaml + - enc-default-staging.secret.values.yaml - name: prod display_name: "University of Toronto (prod)" domain: jupyter.utoronto.ca @@ -28,9 +26,29 @@ hubs: auth0: enabled: false helm_chart_values_files: - # The order in which you list files here is the order the will be passed - # to the helm upgrade command in, and that has meaning. Please check - # that you intend for these files to be applied in this order. - common.values.yaml - - prod.values.yaml - - enc-prod.secret.values.yaml + - default-common.values.yaml + - default-prod.values.yaml + - enc-default-prod.secret.values.yaml + - name: r-staging + display_name: "University of Toronto (r-staging)" + domain: r-staging.utoronto.2i2c.cloud + helm_chart: basehub + auth0: + enabled: false + helm_chart_values_files: + - common.values.yaml + - r-common.values.yaml + - r-staging.values.yaml + - enc-r-staging.secret.values.yaml + - name: r-prod + display_name: "University of Toronto (R)" + domain: r.utoronto.2i2c.cloud + helm_chart: basehub + auth0: + enabled: false + helm_chart_values_files: + - common.values.yaml + - r-common.values.yaml + - r-prod.values.yaml + - enc-r-prod.secret.values.yaml diff --git a/config/clusters/utoronto/common.values.yaml b/config/clusters/utoronto/common.values.yaml index 2fb137fbab..7eed5f9b7a 100644 --- a/config/clusters/utoronto/common.values.yaml +++ b/config/clusters/utoronto/common.values.yaml @@ -64,9 +64,6 @@ jupyterhub: [credential "https://github.com"] helper = !git-credential-github-app --app-key-file /etc/github/github-app-private-key.pem --app-id 93515 useHttpPath = true - image: - name: quay.io/2i2c/utoronto-image - tag: "445cbd1f113b" hub: db: pvc: @@ -77,16 +74,12 @@ jupyterhub: config: Authenticator: enable_auth_state: false - admin_users: - - 7c76d04b-2a80-4db1-b985-a2d2fa2f708c - - 09056164-42f5-4113-9fd7-dd852e63ff1d - - adb7ebad-9fb8-481a-bc2c-6c0a8b6de670 JupyterHub: authenticator_class: azuread concurrent_spawn_limit: 100 # We wanna keep logs long term, primarily for analytics extra_log_file: /srv/jupyterhub/jupyterhub.log AzureAdOAuthenticator: - username_claim: oid + username_claim: email login_service: "University of Toronto ID" tenant_id: 78aac226-2f03-4b4d-9037-b46d56c55210 diff --git a/config/clusters/utoronto/default-common.values.yaml b/config/clusters/utoronto/default-common.values.yaml new file mode 100644 index 0000000000..d1c20aa327 --- /dev/null +++ b/config/clusters/utoronto/default-common.values.yaml @@ -0,0 +1,16 @@ +jupyterhub: + singleuser: + image: + name: quay.io/2i2c/utoronto-image + tag: "445cbd1f113b" + hub: + config: + Authenticator: + admin_users: + - 7c76d04b-2a80-4db1-b985-a2d2fa2f708c + - 09056164-42f5-4113-9fd7-dd852e63ff1d + - adb7ebad-9fb8-481a-bc2c-6c0a8b6de670 + + AzureAdOAuthenticator: + # Everyone else uses email + username_claim: oid diff --git a/config/clusters/utoronto/prod.values.yaml b/config/clusters/utoronto/default-prod.values.yaml similarity index 100% rename from config/clusters/utoronto/prod.values.yaml rename to config/clusters/utoronto/default-prod.values.yaml diff --git a/config/clusters/utoronto/staging.values.yaml b/config/clusters/utoronto/default-staging.values.yaml similarity index 100% rename from config/clusters/utoronto/staging.values.yaml rename to config/clusters/utoronto/default-staging.values.yaml diff --git a/config/clusters/utoronto/enc-prod.secret.values.yaml b/config/clusters/utoronto/enc-default-prod.secret.values.yaml similarity index 100% rename from config/clusters/utoronto/enc-prod.secret.values.yaml rename to config/clusters/utoronto/enc-default-prod.secret.values.yaml diff --git a/config/clusters/utoronto/enc-staging.secret.values.yaml b/config/clusters/utoronto/enc-default-staging.secret.values.yaml similarity index 100% rename from config/clusters/utoronto/enc-staging.secret.values.yaml rename to config/clusters/utoronto/enc-default-staging.secret.values.yaml diff --git a/config/clusters/utoronto/enc-r-prod.secret.values.yaml b/config/clusters/utoronto/enc-r-prod.secret.values.yaml new file mode 100644 index 0000000000..d5df9d6d35 --- /dev/null +++ b/config/clusters/utoronto/enc-r-prod.secret.values.yaml @@ -0,0 +1,24 @@ +jupyterhub: + singleuser: + extraFiles: + github-app-private-key.pem: + stringData: ENC[AES256_GCM,data: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,iv:sml8ZE7+tO+7MId9/5XQpYA6sMXD9MhQAznzGsQOHV8=,tag:ITulLSSMTWTStGeOrAPP6g==,type:str] + hub: + config: + AzureAdOAuthenticator: + client_id: ENC[AES256_GCM,data:NRIJj6slolCvidb+x0f4xI+mbsis8Bl4BACcca0OhyVrycmG,iv:CA8HR207ItxRul1UaEwNBEbbxvOSagpgkq4VseVlj9s=,tag:q0z0HcLBgcljE6OB0suKmw==,type:str] + client_secret: ENC[AES256_GCM,data:hkfjIVkw3f1t/1TRORLKf16BfCVZ3pr7w5HeIY73hOIj5HNwr5oYzw==,iv:DyC09nr+LnKNQKuA9gghU6MXZnIDjbSag/XGnUwAZm4=,tag:0E3E9K+vhHgg3j6jebZg4w==,type:str] +sops: + kms: [] + gcp_kms: + - resource_id: projects/two-eye-two-see/locations/global/keyRings/sops-keys/cryptoKeys/similar-hubs + created_at: "2022-02-24T18:09:31Z" + enc: CiQA4OM7eOdvNAZ+zUSwT0PqdTYnrcEdl/SXGm7kpyMR1ZOtWnYSSQDm5XgWDUzIYL2PKXD9wHbao/Aqsv6kweTN3XDslnZDyOIFGtYEm1s+8cM9HCWfnSA0UOVQTZoly/a2oc59PnqTdnkLV0iu7dY= + azure_kv: [] + hc_vault: [] + age: [] + lastmodified: "2022-08-31T20:17:48Z" + mac: ENC[AES256_GCM,data:ZHtBcFWZRkZeRv+UB7m7IGtVlo++NeKfu1B777Pfc7eXjnyZcLi99ojVxANB+kA1c7lICmgmNEMPWKxK922ejOBGJN8pAnJjpHxVswWZ1enAzSzkvnsFZoMEZdMkar9zNMK8AjMUeO7139cqnM/yVj72voiCfTS/C5JdJ1hg82w=,iv:2TV5yr9CkDBWJVoi2OPPqXirRs5up4XdpQ4O0qTRDHw=,tag:EDuhGi1hRT73lbs2nsqLDA==,type:str] + pgp: [] + unencrypted_suffix: _unencrypted + version: 3.7.3 diff --git a/config/clusters/utoronto/enc-r-staging.secret.values.yaml b/config/clusters/utoronto/enc-r-staging.secret.values.yaml new file mode 100644 index 0000000000..5578ab26d4 --- /dev/null +++ b/config/clusters/utoronto/enc-r-staging.secret.values.yaml @@ -0,0 +1,24 @@ +jupyterhub: + singleuser: + extraFiles: + github-app-private-key.pem: + stringData: ENC[AES256_GCM,data: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,iv:0/wWZcMZwP9XXASWPCHk1PgmM2gTZEw8hkyZd9lEWCk=,tag:i44AuDtv0bWAzsiQlMg/LQ==,type:str] + hub: + config: + AzureAdOAuthenticator: + client_id: ENC[AES256_GCM,data:082nKaBVlegQiGmtVti7PPBz+T7GLTCyB6910E6EgXnedDJZ,iv:mqE0/hkXIp2leSohgkdgdEB7cy6rgy7nH+2NkJCcZ6o=,tag:Ii23/LAfbXi0F66GPmpmRQ==,type:str] + client_secret: ENC[AES256_GCM,data:DEYYnc/bxBcJtAJ/TBY/IggBbqdp8tVUM1GgdsayEUGelEtvhmbarQ==,iv:5hWqi8Cag0wRYnfA82I8FI58mQUi4Rm0TRaCr764sb0=,tag:uj91hvmqqc2FwJkYeo1Ebw==,type:str] +sops: + kms: [] + gcp_kms: + - resource_id: projects/two-eye-two-see/locations/global/keyRings/sops-keys/cryptoKeys/similar-hubs + created_at: "2022-02-24T18:08:21Z" + enc: CiQA4OM7eBanK+HbBPB2vHvIZKJONEYQrsehrIP5d0u8r0+7R/gSSADm5XgWfJP/jYs7/3IkZmdvNF+pWLMrS5rbDoUJjrqcKlUcDL6u5jrbSXFF/lE4nSuyFrKSxNkMm28dvYAMsqHrZIFwVBLXOg== + azure_kv: [] + hc_vault: [] + age: [] + lastmodified: "2022-09-08T07:48:01Z" + mac: ENC[AES256_GCM,data:rr/BWRngZh08Su8zP+eJ4OqLuT7IbG7/voAh1iPuwHPPLIeeNTnIopelD/6OLjhCbkiZhtij2y6lASwa5xzwmLuvvxiAdQrX6f+ZqOgBZ1KxuJzELQxSY4X2L6cgCeGFnhHhQY1EoYqsGEwjSk6zrPPtR93sBL80ZmL+swHtz3E=,iv:jFbf45ItjJV/ALnyk0xNyy7dnvtcGuIj3+pQNTqWcaM=,tag:mYHTUwzMJSERSvya1OMNGQ==,type:str] + pgp: [] + unencrypted_suffix: _unencrypted + version: 3.7.3 diff --git a/config/clusters/utoronto/r-common.values.yaml b/config/clusters/utoronto/r-common.values.yaml new file mode 100644 index 0000000000..e482cf988a --- /dev/null +++ b/config/clusters/utoronto/r-common.values.yaml @@ -0,0 +1,9 @@ +jupyterhub: + singleuser: + storage: + # From https://jupyterhub-image.guide/rocker.html#step-7-setup-zero-to-jupyterhub-configuration-for-home-directory + homeMountPath: /home/rstudio + defaultUrl: /rstudio + image: + name: quay.io/2i2c/utoronto-r-image + tag: "bd1a9c4eea2e" diff --git a/config/clusters/utoronto/r-prod.values.yaml b/config/clusters/utoronto/r-prod.values.yaml new file mode 100644 index 0000000000..07490173dd --- /dev/null +++ b/config/clusters/utoronto/r-prod.values.yaml @@ -0,0 +1,10 @@ +jupyterhub: + hub: + db: + pvc: + # prod stores logs, so let's make it big + storage: 60Gi + config: + AzureAdOAuthenticator: + oauth_callback_url: https://r.utoronto.2i2c.cloud/hub/oauth_callback + logout_redirect_url: https://login.microsoftonline.com/common/oauth2/logout?post_logout_redirect_uri=https://r.utoronto.2i2c.cloud diff --git a/config/clusters/utoronto/r-staging.values.yaml b/config/clusters/utoronto/r-staging.values.yaml new file mode 100644 index 0000000000..b0478f1d4f --- /dev/null +++ b/config/clusters/utoronto/r-staging.values.yaml @@ -0,0 +1,6 @@ +jupyterhub: + hub: + config: + AzureAdOAuthenticator: + oauth_callback_url: https://r-staging.utoronto.2i2c.cloud/hub/oauth_callback + logout_redirect_url: https://login.microsoftonline.com/common/oauth2/logout?post_logout_redirect_uri=https://r-staging.utoronto.2i2c.cloud From 57ee13852c4d3a167bf1f7555b8325bfc91cdcc6 Mon Sep 17 00:00:00 2001 From: sean-morris Date: Mon, 12 Dec 2022 17:31:21 -0800 Subject: [PATCH 069/105] Added CSM to Cloudbank --- config/clusters/cloudbank/cluster.yaml | 13 ++++++ config/clusters/cloudbank/csm.values.yaml | 54 +++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 config/clusters/cloudbank/csm.values.yaml diff --git a/config/clusters/cloudbank/cluster.yaml b/config/clusters/cloudbank/cluster.yaml index 411e0d1541..756d6fcef3 100644 --- a/config/clusters/cloudbank/cluster.yaml +++ b/config/clusters/cloudbank/cluster.yaml @@ -36,6 +36,19 @@ hubs: # to the helm upgrade command in, and that has meaning. Please check # that you intend for these files to be applied in this order. - ccsf.values.yaml + - name: csm + display_name: "College of San Mateo" + domain: csm.cloudbank.2i2c.cloud + helm_chart: basehub + auth0: + # connection update? Also ensure the basehub Helm chart is provided a + # matching value for jupyterhub.custom.2i2c.add_staff_user_ids_of_type! + connection: google-oauth2 + helm_chart_values_files: + # The order in which you list files here is the order the will be passed + # to the helm upgrade command in, and that has meaning. Please check + # that you intend for these files to be applied in this order. + - csm.values.yaml - name: elcamino display_name: "El Camino College" domain: elcamino.cloudbank.2i2c.cloud diff --git a/config/clusters/cloudbank/csm.values.yaml b/config/clusters/cloudbank/csm.values.yaml new file mode 100644 index 0000000000..46fee6b1cb --- /dev/null +++ b/config/clusters/cloudbank/csm.values.yaml @@ -0,0 +1,54 @@ +jupyterhub: + custom: + 2i2c: + add_staff_user_ids_to_admin_users: true + add_staff_user_ids_of_type: "google" + homepage: + templateVars: + org: + name: College of San Mateo + logo_url: https://collegeofsanmateo.edu/marketing/images/logos/CSM_Monogram_process_blue.jpg + url: https://collegeofsanmateo.edu/ + designed_by: + name: 2i2c + url: https://2i2c.org + operated_by: + name: CloudBank + url: http://cloudbank.org/ + funded_by: + name: CloudBank + url: http://cloudbank.org/ + hub: + config: + Authenticator: + allowed_users: &csm_users + - ericvd@berkeley.edu + - k_usovich@berkeley.edu + - sean.smorris@berkeley.edu + - pachecoh@smccd.edu + admin_users: *csm_users + extraFiles: + configurator-schema-default: + data: + properties: + Spawner.default_url: + type: string + title: Default User Interface + enum: + - "/tree" + - "/lab" + - "/retro" + default: "/retro" + enumMetadata: + interfaces: + - value: "/tree" + title: Classic Notebook + description: + The original single-document interface for creating + Jupyter Notebooks. + - value: "/lab" + title: JupyterLab + description: A Powerful next generation notebook interface + - value: "/retro" + title: RetroLab + description: A JupyterLab distribution with a retro look and feel, more similar to the classic Jupyter notebook. From 8882941e4f276c656655c8d6b161bfc862540c6d Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Mon, 12 Dec 2022 13:21:23 -0800 Subject: [PATCH 070/105] Add deployment to measure free space in hub home dir This adds a deployment with a single pod of [node_exporter](https://github.com/prometheus/node_exporter) running, configured only to report filesystem statistics. We mount the same home directory configuration that is used in the hub to this pod, so it will provide us with information on total disk size and used disk. This would allow us to have a Grafana panel with 'disk used %' for each of these hubs! We can even alert on those later. --- .../templates/home-space-reporter.yaml | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 helm-charts/basehub/templates/home-space-reporter.yaml diff --git a/helm-charts/basehub/templates/home-space-reporter.yaml b/helm-charts/basehub/templates/home-space-reporter.yaml new file mode 100644 index 0000000000..8134f12e04 --- /dev/null +++ b/helm-charts/basehub/templates/home-space-reporter.yaml @@ -0,0 +1,58 @@ +# Deploy a prometheus node_exporter with the same home directory +# we have for our hub mounted so we can monitor free space usage. +{{- if or .Values.nfs.enabled .Values.azureFile.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: jupyterhub + component: home-metrics + name: home-metrics +spec: + replicas: 1 + selector: + matchLabels: + app: jupyterhub + component: home-metrics + template: + metadata: + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9100" + labels: + app: jupyterhub + component: home-metrics + spec: + containers: + - args: + # We only want filesystem stats + - --collector.disable-defaults + - --collector.filesystem + - --web.listen-address=:9100 + image: quay.io/prometheus/node-exporter:v1.3.1 + name: home-directory-exporter + ports: + - containerPort: 9100 + name: metrics + protocol: TCP + securityContext: + allowPrivilegeEscalation: false + volumeMounts: + - name: home + mountPath: /home + # Mount it readonly to prevent accidental writes + readOnly: true + securityContext: + fsGroup: 65534 + runAsGroup: 65534 + runAsNonRoot: true + runAsUser: 65534 + volumes: + - name: home + persistentVolumeClaim: + {{- if .Values.azureFile.enabled }} + claimName: home-azurefile + {{- else }} + claimName: home-nfs + {{- end }} +{{- end }} From 15b801917e696e31aa3c172692329eca17d96b9b Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 13 Dec 2022 17:11:02 -0800 Subject: [PATCH 071/105] Add info about uid node_exporter is run as --- helm-charts/basehub/templates/home-space-reporter.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/helm-charts/basehub/templates/home-space-reporter.yaml b/helm-charts/basehub/templates/home-space-reporter.yaml index 8134f12e04..fcac3f453c 100644 --- a/helm-charts/basehub/templates/home-space-reporter.yaml +++ b/helm-charts/basehub/templates/home-space-reporter.yaml @@ -43,6 +43,9 @@ spec: # Mount it readonly to prevent accidental writes readOnly: true securityContext: + # This is the 'nobody' user that the node-exporter image expects + # to be run as, and has no privileges. + # https://wiki.ubuntu.com/nobody has more info fsGroup: 65534 runAsGroup: 65534 runAsNonRoot: true From f6834c3b9055952fa11ec85deb5bfcaf94efb78b Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 13 Dec 2022 10:25:06 -0800 Subject: [PATCH 072/105] Explicitly json encode output to GITHUB_ENV An attempt to fix up https://github.com/2i2c-org/infrastructure/actions/runs/3687975422, which currently fails with: Error when evaluating 'strategy' for job 'validate-helm-charts-values-files'. .github/workflows/validate-clusters.yaml (Line: 190, Col: 15): Error parsing fromJson,.github/workflows/validate-clusters.yaml (Line: 190, Col: 15): Error reading JToken from JsonReader. Path '', line 0, position 0.,.github/workflows/validate-clusters.yaml (Line: 190, Col: 15): Unexpected type of value '', expected type: Sequence. The JSON it's trying to read, I believe, is: > matrix='[{'cluster_name': 'utoronto'}]' If I try out with some python, ```python >>> matrix=[{"cluster_name": "utoronto"}] >>> f"{matrix}" "[{'cluster_name': 'utoronto'}]" >>> import json >>> f"{json.dumps(matrix)}" '[{"cluster_name": "utoronto"}]' ``` You'll see that json.dumps produces double quotes, while just plain output produces single quotes. I don't actually know if this is the exact problem, nor do I know why it would be an issue *now* when it has not been before! But I'm going to try. Also setting the MATRIX env var to be all caps, for consistency with the other place we set MATRIX --- .github/workflows/validate-clusters.yaml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/validate-clusters.yaml b/.github/workflows/validate-clusters.yaml index f210d0c0cf..f43f58466f 100644 --- a/.github/workflows/validate-clusters.yaml +++ b/.github/workflows/validate-clusters.yaml @@ -109,6 +109,7 @@ jobs: shell: python run: | import os + import json # List all cluster folders cluster_folders = os.listdir("config/clusters") @@ -126,7 +127,10 @@ jobs: # Write matrix to the GITHUB_ENV file in GitHub Actions env_file = os.getenv("GITHUB_ENV") with open(env_file, "a") as f: - f.write(f"MATRIX={matrix}") + # Explicitly dump these as JSON, as that is what they are read as + # General python object syntax sometimes works but sometimes does + # not - for example, single quotes are not valid in JSON. + f.write(f"MATRIX={json.dumps(matrix)}") # Only run this step if there are *NO* changes under the common filter, # but *ARE* changes under the cluster_specific filter, *AND* we have not @@ -139,6 +143,7 @@ jobs: shell: python run: | import os + import json from pathlib import Path # Consume list of changed cluster files and convert to list by splitting @@ -164,7 +169,10 @@ jobs: # Write the matrix to the GITHUB_ENV file in GitHub Actions env_file = os.getenv("GITHUB_ENV") with open(env_file, "a") as f: - f.write(f"matrix={matrix}") + # Explicitly dump these as JSON, as that is what they are read as + # General python object syntax sometimes works but sometimes does + # not - for example, single quotes are not valid in JSON. + f.write(f"MATRIX={json.dumps(matrix)}") # This job runs the 'deployer validate' subcommand across a matrix of # cluster names. From cc9e745d107d34330b4fbd121f539dd4bc3aee05 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Mon, 28 Nov 2022 19:02:20 -0800 Subject: [PATCH 073/105] Give all students equal resources during exam During an exam, it is important that all students have access to the same computational resources. 2G should be enough, given it was the limit for everyone in general. We are *adding* a CPU limit here, where we did not have one before - so this must be validated beforehand to make sure it doesn't have adverse effects. Ref https://github.com/2i2c-org/infrastructure/issues/1905 --- config/clusters/utoronto/common.values.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/config/clusters/utoronto/common.values.yaml b/config/clusters/utoronto/common.values.yaml index 7eed5f9b7a..d8e93c6d1d 100644 --- a/config/clusters/utoronto/common.values.yaml +++ b/config/clusters/utoronto/common.values.yaml @@ -48,8 +48,12 @@ jupyterhub: singleuser: memory: + # Ensure all students get equal resources during the exam limit: 2G - guarantee: 1G + guarantee: 2G + cpu: + limit: 1 + guarantee: 1 extraFiles: github-app-private-key.pem: mountPath: /etc/github/github-app-private-key.pem From 01f537ed9cf7cee3a5462edefcbbba2319700123 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Tue, 13 Dec 2022 13:21:28 +0200 Subject: [PATCH 074/105] Add instructions about how to scale up an Azure cluster --- docs/images/azure-scale-node-pool-window.png | Bin 0 -> 183822 bytes docs/sre-guide/node-scale-up/azure.md | 46 +++++++++++++++++++ docs/sre-guide/node-scale-up/index.md | 3 +- 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 docs/images/azure-scale-node-pool-window.png create mode 100644 docs/sre-guide/node-scale-up/azure.md diff --git a/docs/images/azure-scale-node-pool-window.png b/docs/images/azure-scale-node-pool-window.png new file mode 100644 index 0000000000000000000000000000000000000000..d3d59713f670e1cafc145dcc8a22faa7061eca15 GIT binary patch literal 183822 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

TAR01NR)M6n?dQjRZMP8?A>Z$LvA9c4#Zy*^t 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 Date: Tue, 13 Dec 2022 13:34:19 +0200 Subject: [PATCH 075/105] Indent the image differently --- docs/sre-guide/node-scale-up/azure.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/sre-guide/node-scale-up/azure.md b/docs/sre-guide/node-scale-up/azure.md index b1df23a8d7..4859b0453a 100644 --- a/docs/sre-guide/node-scale-up/azure.md +++ b/docs/sre-guide/node-scale-up/azure.md @@ -19,9 +19,10 @@ server startup faster. 1. A new window should pop up that looks like this - ```{figure} - ../../images/azure-scale-node-pool-window.png - ``` +```{figure} +../../images/azure-scale-node-pool-window.png +``` + 1. If the `Autoscale` option is selected like in the screenshot above (the default, recommended option), then in order to scale up the nodepool to an exact number of nodes, temporarily deactivate the autoscaler, by selecting the `Manual` option, introduce the desired number of nodes then click `Apply`. From 81e9801fd3ceecfdaf8b8d97f9a736fbdd24e618 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Tue, 13 Dec 2022 13:41:50 +0200 Subject: [PATCH 076/105] Get the image to render --- docs/sre-guide/node-scale-up/azure.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sre-guide/node-scale-up/azure.md b/docs/sre-guide/node-scale-up/azure.md index 4859b0453a..9b87deab45 100644 --- a/docs/sre-guide/node-scale-up/azure.md +++ b/docs/sre-guide/node-scale-up/azure.md @@ -19,9 +19,9 @@ server startup faster. 1. A new window should pop up that looks like this -```{figure} -../../images/azure-scale-node-pool-window.png -``` + ```{figure} ../../images/azure-scale-node-pool-window.png + Scale nodepool + ``` 1. If the `Autoscale` option is selected like in the screenshot above (the default, recommended option), then in order to scale up the nodepool to an exact number of nodes, temporarily deactivate the autoscaler, From bdf2b3b32bc0e68fe7297cd57472287ac88898b3 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Wed, 14 Dec 2022 09:54:15 +0200 Subject: [PATCH 077/105] Fix spelling Co-authored-by: Sarah Gibson --- docs/sre-guide/node-scale-up/azure.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/sre-guide/node-scale-up/azure.md b/docs/sre-guide/node-scale-up/azure.md index 9b87deab45..1c01b69ea1 100644 --- a/docs/sre-guide/node-scale-up/azure.md +++ b/docs/sre-guide/node-scale-up/azure.md @@ -2,7 +2,7 @@ # Azure In certain cases, it might be helpful to 'scale up' -a nodegroup in a cluster before an event, to test cloud provider quotas or to make user +a node group in a cluster before an event, to test cloud provider quotas or to make user server startup faster. ## Azure Console UI instructions @@ -13,18 +13,18 @@ server startup faster. 1. Click on the cluster name, then go to `Settings` and select the `Node pools` option -1. Click on the appropriate nodepool that you would like to scale up +1. Click on the appropriate node pool that you would like to scale up 1. Then select the `Scale node pool` option from the top of the page 1. A new window should pop up that looks like this ```{figure} ../../images/azure-scale-node-pool-window.png - Scale nodepool + Scale node pool ``` 1. If the `Autoscale` option is selected like in the screenshot above (the default, recommended option), - then in order to scale up the nodepool to an exact number of nodes, temporarily deactivate the autoscaler, + then in order to scale up the node pool to an exact number of nodes, temporarily deactivate the autoscaler, by selecting the `Manual` option, introduce the desired number of nodes then click `Apply`. 1. After the Apply succeded, you should see the new nodes coming up. @@ -32,7 +32,7 @@ server startup faster. and set the min number of nodes to the desired one the you set in the step before. ```{warning} -The cluster autoscaler doesn't enforce the nodepool size after updating the `Min` or `Max` counts. +The cluster autoscaler doesn't enforce the node pool size after updating the `Min` or `Max` counts. The limits will be taken into accounts for future scaling decisions. A new scaling decision happens after a scale up/down event is triggered. From 322e7972c98bcb356c233c262bd329af1cf5ae92 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Wed, 14 Dec 2022 10:05:18 +0200 Subject: [PATCH 078/105] Add warning and scaling down instructions --- docs/sre-guide/node-scale-up/azure.md | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/docs/sre-guide/node-scale-up/azure.md b/docs/sre-guide/node-scale-up/azure.md index 1c01b69ea1..01fc6b7fda 100644 --- a/docs/sre-guide/node-scale-up/azure.md +++ b/docs/sre-guide/node-scale-up/azure.md @@ -7,6 +7,8 @@ server startup faster. ## Azure Console UI instructions +### Scaling up a node pool + 1. Login to https://portal.azure.com using the appropriate cluster credentials 1. Search the `Kubernetes service` section for the appropriate cluster @@ -28,10 +30,26 @@ server startup faster. by selecting the `Manual` option, introduce the desired number of nodes then click `Apply`. 1. After the Apply succeded, you should see the new nodes coming up. - You can then click on `Scale node pool` option again, enable the `Autoscale`, - and set the min number of nodes to the desired one the you set in the step before. + You can then click on `Scale node pool` option again, **enable the `Autoscale`**, + and set the `Min` number of nodes to the desired one the you set in the step before. ```{warning} +Don't forget to turn the autoscaler back on after the manual +modification of the node pool size! This is **really important**, otherwise +a scale up from the max manual limit, won't be able to happen automatically, +and the hub won't be able to spawn new user servers. +``` + +### Scaling down a node pool + +1. Follow the first six steps in the scaling up guide above, until you get to the `Scale node pool` window. + +1. This time, **do not activate the `Manual` mode**, + and just adjust the `Min` number of nodes for the autoscaler. + As users stop their servers after the event, eventually a scale down event will be triggered, + and the autoscaler will adjust the node pool size according to the limits that are set. + +```{note} The cluster autoscaler doesn't enforce the node pool size after updating the `Min` or `Max` counts. The limits will be taken into accounts for future scaling decisions. A new scaling decision happens after a scale up/down event is triggered. From 198b46f651cb8c64f635a9fc567e93cdc47bf5b7 Mon Sep 17 00:00:00 2001 From: Sarah Gibson <44771837+sgibson91@users.noreply.github.com> Date: Wed, 14 Dec 2022 18:06:52 +0000 Subject: [PATCH 079/105] Revert "[Merge on Dec 14, 2022] Give all students equal resources during exam" --- config/clusters/utoronto/common.values.yaml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/config/clusters/utoronto/common.values.yaml b/config/clusters/utoronto/common.values.yaml index d8e93c6d1d..7eed5f9b7a 100644 --- a/config/clusters/utoronto/common.values.yaml +++ b/config/clusters/utoronto/common.values.yaml @@ -48,12 +48,8 @@ jupyterhub: singleuser: memory: - # Ensure all students get equal resources during the exam limit: 2G - guarantee: 2G - cpu: - limit: 1 - guarantee: 1 + guarantee: 1G extraFiles: github-app-private-key.pem: mountPath: /etc/github/github-app-private-key.pem From dad0d26b40ef4ff5529d63865b311e97c0616354 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 13 Dec 2022 12:27:10 -0800 Subject: [PATCH 080/105] Enable named servers for m2lines & pangeo-hubs I think we should enable this for all research hubs. Based on request by @dhruvbalwada --- config/clusters/m2lines/common.values.yaml | 1 + config/clusters/pangeo-hubs/common.values.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/config/clusters/m2lines/common.values.yaml b/config/clusters/m2lines/common.values.yaml index a0f355a71b..2acf8e7519 100644 --- a/config/clusters/m2lines/common.values.yaml +++ b/config/clusters/m2lines/common.values.yaml @@ -33,6 +33,7 @@ basehub: name: M²LInES url: https://m2lines.github.io/ hub: + allowNamedServers: true config: Authenticator: # This hub uses GitHub Teams auth and so we don't set diff --git a/config/clusters/pangeo-hubs/common.values.yaml b/config/clusters/pangeo-hubs/common.values.yaml index 4aa2bc0543..04bfcb99c6 100644 --- a/config/clusters/pangeo-hubs/common.values.yaml +++ b/config/clusters/pangeo-hubs/common.values.yaml @@ -39,6 +39,7 @@ basehub: name: NSF EarthCube Program (Award ICER-2026932) url: "https://www.nsf.gov/awardsearch/showAward?AWD_ID=2026932" hub: + allowNamedServers: true config: Authenticator: # This hub uses GitHub Teams auth and so we don't set From 4d1613d4e88e0ed2b697f5795c84bc99457d0257 Mon Sep 17 00:00:00 2001 From: sean-morris Date: Thu, 15 Dec 2022 11:05:58 -0800 Subject: [PATCH 081/105] Updated CloudBank CSM College of San Mateo uses Google for third party authentication. --- config/clusters/cloudbank/cluster.yaml | 3 ++- config/clusters/cloudbank/csm.values.yaml | 10 ++++++++-- .../cloudbank/enc-csm.secret.values.yaml | 20 +++++++++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 config/clusters/cloudbank/enc-csm.secret.values.yaml diff --git a/config/clusters/cloudbank/cluster.yaml b/config/clusters/cloudbank/cluster.yaml index 756d6fcef3..a8d1631c67 100644 --- a/config/clusters/cloudbank/cluster.yaml +++ b/config/clusters/cloudbank/cluster.yaml @@ -43,12 +43,13 @@ hubs: auth0: # connection update? Also ensure the basehub Helm chart is provided a # matching value for jupyterhub.custom.2i2c.add_staff_user_ids_of_type! - connection: google-oauth2 + enabled: false helm_chart_values_files: # The order in which you list files here is the order the will be passed # to the helm upgrade command in, and that has meaning. Please check # that you intend for these files to be applied in this order. - csm.values.yaml + - enc-csm.secret.values.yaml - name: elcamino display_name: "El Camino College" domain: elcamino.cloudbank.2i2c.cloud diff --git a/config/clusters/cloudbank/csm.values.yaml b/config/clusters/cloudbank/csm.values.yaml index 46fee6b1cb..d072dc8aee 100644 --- a/config/clusters/cloudbank/csm.values.yaml +++ b/config/clusters/cloudbank/csm.values.yaml @@ -20,13 +20,19 @@ jupyterhub: url: http://cloudbank.org/ hub: config: + JupyterHub: + authenticator_class: cilogon + CILogonOAuthenticator: + oauth_callback_url: https://csm.cloudbank.2i2c.cloud/hub/oauth_callback + username_claim: email Authenticator: - allowed_users: &csm_users + admin_users: - ericvd@berkeley.edu - k_usovich@berkeley.edu - sean.smorris@berkeley.edu - pachecoh@smccd.edu - admin_users: *csm_users + - hellenpacheco@my.smccd.edu + username_pattern: '^(.+@2i2c\.org|.+@berkeley\.edu|.+@my\.smccd\.edu|.+@smccd\.edu|deployment-service-check)$' extraFiles: configurator-schema-default: data: diff --git a/config/clusters/cloudbank/enc-csm.secret.values.yaml b/config/clusters/cloudbank/enc-csm.secret.values.yaml new file mode 100644 index 0000000000..e1ae2901d4 --- /dev/null +++ b/config/clusters/cloudbank/enc-csm.secret.values.yaml @@ -0,0 +1,20 @@ +jupyterhub: + hub: + config: + CILogonOAuthenticator: + client_id: ENC[AES256_GCM,data:GQCsROpo+xCxylVXdYSPbtHE3Z7ospBOOedxM4T+C+tcRjODcH6jSEf0MaG6Q84gixO5,iv:yadRwo2DaR8Xn5B1DzWxl314CbRcFK9esA6T67UagVM=,tag:VKaSAH+/h3sgpRrPC8hUqw==,type:str] + client_secret: ENC[AES256_GCM,data:HjLNL2ZSrDDOa+8JXFB3kNWhi/CH0IbrbNzEJLScK9vDEHL99Uie3npiRudSD01uiVmjnJb+biHywxFufzwDC9MkukZWH14R/NaIcwUUfvANeIWPt8g=,iv:A7lQLmPoof4u7unfI/NzbhxN51PYiG1X29J9WTatrTA=,tag:ptQF7JQOHpJI1ZdMG5ipPw==,type:str] +sops: + kms: [] + gcp_kms: + - resource_id: projects/two-eye-two-see/locations/global/keyRings/sops-keys/cryptoKeys/similar-hubs + created_at: "2022-12-15T19:03:07Z" + enc: CiUA4OM7eFM07UOM/KE5+uDY/L+fNHNcNPAjh8uFHGpJ7ar74LFBEkkA+0T9hWL+MO6J0koNwAAG8FNc491ENtY5W0ejiORbHV6Cs4UMFAbg/+jrSyaykA6TTpYutsqaNtThNoa2OTvlqyJm9N+QvxyW + azure_kv: [] + hc_vault: [] + age: [] + lastmodified: "2022-12-15T19:03:08Z" + mac: ENC[AES256_GCM,data:j+HUs73CebjLFBEy6yyacOj1Mq8A5dDXkjvRD18GdxoIpks+K8gm6RwEF4xKsu24jSEMganS6gttbnvSkU7pyJJ4PcJItnpFnqEw3+b+ohbpmznYtp0Z5/SMoI0olcEzICEAsIlV0XxyfDjF7Mk+mz1xW0gopEbzwtLCWMSQRDI=,iv:rIercrpRBQNELT8wbYljWAUBEFO7ZIFsG7j9SsGRREQ=,tag:QYBjeC3Ao2CSTdDy9W+f0A==,type:str] + pgp: [] + unencrypted_suffix: _unencrypted + version: 3.7.1 From 70bbda4cc9b730fc11afb174d9a64b340074b60b Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 13 Dec 2022 15:21:37 -0800 Subject: [PATCH 082/105] Set rstudio as default selected option in UToronto R hub Ref https://github.com/2i2c-org/infrastructure/issues/1961 --- config/clusters/utoronto/r-common.values.yaml | 5 +++++ helm-charts/basehub/values.schema.yaml | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/config/clusters/utoronto/r-common.values.yaml b/config/clusters/utoronto/r-common.values.yaml index e482cf988a..4065427b46 100644 --- a/config/clusters/utoronto/r-common.values.yaml +++ b/config/clusters/utoronto/r-common.values.yaml @@ -1,4 +1,9 @@ jupyterhub: + custom: + homepage: + templateVars: + # This sets the default selected interface option + default_url: /rstudio singleuser: storage: # From https://jupyterhub-image.guide/rocker.html#step-7-setup-zero-to-jupyterhub-configuration-for-home-directory diff --git a/helm-charts/basehub/values.schema.yaml b/helm-charts/basehub/values.schema.yaml index 223c62f48b..71e708bc09 100644 --- a/helm-charts/basehub/values.schema.yaml +++ b/helm-charts/basehub/values.schema.yaml @@ -292,6 +292,14 @@ properties: - operated_by - funded_by properties: + default_url: + type: string + description: | + Default Interface to be selected in the interface selector + + /tree -> classic notebook + /rstudio -> RStudio + /lab -> JupyterLab announcements: type: array items: From 6f0c41620e690f729701f7ac6317369caefaa5a8 Mon Sep 17 00:00:00 2001 From: Ian Allison Date: Wed, 14 Dec 2022 15:46:27 -0800 Subject: [PATCH 083/105] Add epsb.ca to allowed domains and order alphabetically Signed-off-by: Ian Allison --- config/clusters/callysto/common.values.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/config/clusters/callysto/common.values.yaml b/config/clusters/callysto/common.values.yaml index 89bd9607ea..b904bdf72c 100644 --- a/config/clusters/callysto/common.values.yaml +++ b/config/clusters/callysto/common.values.yaml @@ -109,22 +109,22 @@ jupyterhub: config: EmailAuthenticatingCILogonOAuthenticator: allowed_domains: + - 2i2c.org + - btps.ca - callysto.ca + - cssd.ab.ca - cybera.ca - - wsrd.ca + - eics.ab.ca + - eips.ca + - epsb.ca - fmpsd.ab.ca - fmcsd.ab.ca - - eips.ca - - g.eips.ca - - eics.ab.ca - rvschools.ab.ca - - cssd.ab.ca - - btps.ca + - spschools.org + - wsrd.ca - ucalgary.ca - ualberta.ca - - spschools.org - "*.ca" - - 2i2c.org CILogonOAuthenticator: # We set up admin_users, but *not* allowed users. Those are set up via the extraConfig admin_users: &callysto_users From d0f246d5ce04f09fae168b4ecc1d7b7b2242098c Mon Sep 17 00:00:00 2001 From: Ian Allison Date: Wed, 14 Dec 2022 11:43:41 -0800 Subject: [PATCH 084/105] Bumping image version to include new packages Byron wants haversine installed. It has been added to this environment and relocked as https://github.com/callysto/2i2c-image/commit/1c8498b404f2e8d9863a53d89bb89e0c3d5b6d89 Signed-off-by: Ian Allison --- config/clusters/callysto/common.values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/clusters/callysto/common.values.yaml b/config/clusters/callysto/common.values.yaml index b904bdf72c..c52a2dac77 100644 --- a/config/clusters/callysto/common.values.yaml +++ b/config/clusters/callysto/common.values.yaml @@ -39,7 +39,7 @@ jupyterhub: singleuser: image: name: callysto/2i2c - tag: 0.1.1 + tag: 0.1.2 extraFiles: tree.html: mountPath: /usr/local/share/jupyter/custom_template/tree.html From 135ad3c1cda1e2068080f86bc2748de1c006a029 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Dec 2022 01:48:33 +0000 Subject: [PATCH 085/105] Bump azure/setup-helm from 3.4 to 3.5 in /.github/actions/setup-deploy Bumps [azure/setup-helm](https://github.com/azure/setup-helm) from 3.4 to 3.5. - [Release notes](https://github.com/azure/setup-helm/releases) - [Commits](https://github.com/azure/setup-helm/compare/v3.4...v3.5) --- updated-dependencies: - dependency-name: azure/setup-helm dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/actions/setup-deploy/action.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup-deploy/action.yaml b/.github/actions/setup-deploy/action.yaml index fe47c91ce5..9dca48bd18 100644 --- a/.github/actions/setup-deploy/action.yaml +++ b/.github/actions/setup-deploy/action.yaml @@ -69,7 +69,7 @@ runs: shell: bash # This action use the github official cache mechanism internally - - uses: azure/setup-helm@v3.4 + - uses: azure/setup-helm@v3.5 with: # version is pinned for helm to avoid an automatic update of its version # which would cause something unexpected without an action on our From 44265807810aaf539ba653eebcfb1a288378f2fe Mon Sep 17 00:00:00 2001 From: sean-morris Date: Thu, 22 Dec 2022 10:28:18 -0800 Subject: [PATCH 086/105] CloudBank: Converted SJCC to CiLogon - modified cluster.yaml - created enc-sjcc.secret.values - updated sjcc values file --- config/clusters/cloudbank/cluster.yaml | 3 ++- .../cloudbank/enc-sjcc.secret.values.yaml | 20 +++++++++++++++++++ config/clusters/cloudbank/sjcc.values.yaml | 14 +++++++++---- 3 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 config/clusters/cloudbank/enc-sjcc.secret.values.yaml diff --git a/config/clusters/cloudbank/cluster.yaml b/config/clusters/cloudbank/cluster.yaml index a8d1631c67..66cf525a4a 100644 --- a/config/clusters/cloudbank/cluster.yaml +++ b/config/clusters/cloudbank/cluster.yaml @@ -230,12 +230,13 @@ hubs: auth0: # connection update? Also ensure the basehub Helm chart is provided a # matching value for jupyterhub.custom.2i2c.add_staff_user_ids_of_type! - connection: google-oauth2 + enabled: false helm_chart_values_files: # The order in which you list files here is the order the will be passed # to the helm upgrade command in, and that has meaning. Please check # that you intend for these files to be applied in this order. - sjcc.values.yaml + - enc-sjcc.secret.values.yaml - name: tuskegee display_name: "Tuskegee University" domain: tuskegee.cloudbank.2i2c.cloud diff --git a/config/clusters/cloudbank/enc-sjcc.secret.values.yaml b/config/clusters/cloudbank/enc-sjcc.secret.values.yaml new file mode 100644 index 0000000000..2c8da219ce --- /dev/null +++ b/config/clusters/cloudbank/enc-sjcc.secret.values.yaml @@ -0,0 +1,20 @@ +jupyterhub: + hub: + config: + CILogonOAuthenticator: + client_id: ENC[AES256_GCM,data:LYsHnOvHo+YohdTVVSo1/SwjuMQalAAehC2bvuNoJuQ5DRIIJOlasE85XyhXNBeMvUaA,iv:TjbIYeftupwXepflUgSOekXXTui88ywPWJDwXttiLwo=,tag:ZKgZJD+ZqOEeaPw7ciS/9w==,type:str] + client_secret: ENC[AES256_GCM,data:PVNbwuDF7Kx+n4a8gBfi3k1ne4gJ2ATzKB1qwMbBpJ89fJaADDV7KBetTtnqap5zGKxD9WJ2Kg8qrCwVDMM0RZQHOUwoISawoq3M6uxC7AKUHZzYDTA=,iv:ymRoLsOah0jfQExo0Cc6SFYuuuhLjqe4pZvOkDLRCtc=,tag:SRic3qFWgtuwhau2pSO5pg==,type:str] +sops: + kms: [] + gcp_kms: + - resource_id: projects/two-eye-two-see/locations/global/keyRings/sops-keys/cryptoKeys/similar-hubs + created_at: "2022-12-22T18:26:51Z" + enc: CiUA4OM7eOow6Jope46vV7W6dr8vCsC0UOcZnf0+qE8Wpwtb7EIaEkkA+0T9heSlokmeU2HZNeM0EMPVDUDAcS8P3nCNSFP3SxVFnZz2DKZVoNAp9IA52MTf/0Qi17sf9PwiqOO7x3kp8fTmEMUGZOTa + azure_kv: [] + hc_vault: [] + age: [] + lastmodified: "2022-12-22T18:26:52Z" + mac: ENC[AES256_GCM,data:/8nS+MnLo8PCPYJ3XDt8ghyPuYwLmQLLtHEH5jEl2ACkRsLV4fLxwzZag6TFailzW1P7ivg1DatCU6F6wkwrTDtZcF08wgk2a2hGN6gjqTrwPMr+EFfEci3buXxj7l/nWmGrP4BUr+i1V9FBoV7BIVrpYqv4Qy36/Qe/ixZoT24=,iv:VMT4FKU79eVrsft3A6hWpZkwddax2FkGTB8mApyRyY4=,tag:V51uv0Z/gr5FQO6p72lOWw==,type:str] + pgp: [] + unencrypted_suffix: _unencrypted + version: 3.7.1 diff --git a/config/clusters/cloudbank/sjcc.values.yaml b/config/clusters/cloudbank/sjcc.values.yaml index d9a2292cb8..f2c1d3ad15 100644 --- a/config/clusters/cloudbank/sjcc.values.yaml +++ b/config/clusters/cloudbank/sjcc.values.yaml @@ -20,10 +20,16 @@ jupyterhub: url: http://cloudbank.org/ hub: config: + JupyterHub: + authenticator_class: cilogon + CILogonOAuthenticator: + oauth_callback_url: https://sjcc.cloudbank.2i2c.cloud/hub/oauth_callback + username_claim: email Authenticator: - allowed_users: &sjcc_users - - aculich@berkeley.edu - - sean.smorris@berkeley.edu + admin_users: - christiaan.desmond@sjcc.edu - sanjay.dorairaj@sjcc.edu - admin_users: *sjcc_users + - ericvd@berkeley.edu + - k_usovich@berkeley.edu + - sean.smorris@berkeley.edu + username_pattern: '^(.+@2i2c\.org|.+@berkeley\.edu|.+@sjcc\.edu|.+@evc\.edu|deployment-service-check)$' From 89312a4a8d7a66e48c1a8f28889c837ec64ed09c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 19 Dec 2022 20:52:41 +0000 Subject: [PATCH 087/105] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/isort: 5.11.0 → v5.11.3](https://github.com/pycqa/isort/compare/5.11.0...v5.11.3) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1ce127be20..b385d2ef38 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,7 +28,7 @@ repos: # Autoformat: Python code - repo: https://github.com/pycqa/isort - rev: "5.11.0" + rev: "v5.11.3" hooks: - id: isort From ec5b4178d043ef75ac047a6a60d030952c2083fd Mon Sep 17 00:00:00 2001 From: betolink Date: Tue, 20 Dec 2022 15:01:06 -0600 Subject: [PATCH 088/105] update python image --- config/clusters/openscapes/common.values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/clusters/openscapes/common.values.yaml b/config/clusters/openscapes/common.values.yaml index 545a7f3e91..176be5eb89 100644 --- a/config/clusters/openscapes/common.values.yaml +++ b/config/clusters/openscapes/common.values.yaml @@ -60,7 +60,7 @@ basehub: default: true slug: "python" kubespawner_override: - image: openscapes/python:431a94c + image: openscapes/python:11de2c5 rocker: display_name: R slug: "rocker" From b1282cd966ea6e77d93127d4336c322834adc420 Mon Sep 17 00:00:00 2001 From: betolink Date: Tue, 20 Dec 2022 15:14:21 -0600 Subject: [PATCH 089/105] update R image --- config/clusters/openscapes/common.values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/clusters/openscapes/common.values.yaml b/config/clusters/openscapes/common.values.yaml index 176be5eb89..9a2f602c97 100644 --- a/config/clusters/openscapes/common.values.yaml +++ b/config/clusters/openscapes/common.values.yaml @@ -65,7 +65,7 @@ basehub: display_name: R slug: "rocker" kubespawner_override: - image: openscapes/rocker:b88a034 + image: openscapes/rocker:53180c0 matlab: display_name: Matlab slug: "matlab" From 0f4f7e48da687ea3ca65fd5bf027ca9a2d813e93 Mon Sep 17 00:00:00 2001 From: betolink Date: Tue, 20 Dec 2022 15:20:37 -0600 Subject: [PATCH 090/105] separate staging from prod --- config/clusters/openscapes/cluster.yaml | 2 +- config/clusters/openscapes/common.values.yaml | 4 +- .../clusters/openscapes/staging.values.yaml | 132 ++++++++++++++++++ 3 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 config/clusters/openscapes/staging.values.yaml diff --git a/config/clusters/openscapes/cluster.yaml b/config/clusters/openscapes/cluster.yaml index c30179050d..419f100109 100644 --- a/config/clusters/openscapes/cluster.yaml +++ b/config/clusters/openscapes/cluster.yaml @@ -23,7 +23,7 @@ hubs: # The order in which you list files here is the order the will be passed # to the helm upgrade command in, and that has meaning. Please check # that you intend for these files to be applied in this order. - - common.values.yaml + - staging.values.yaml - name: prod display_name: "Openscapes (prod)" domain: openscapes.2i2c.cloud diff --git a/config/clusters/openscapes/common.values.yaml b/config/clusters/openscapes/common.values.yaml index 9a2f602c97..545a7f3e91 100644 --- a/config/clusters/openscapes/common.values.yaml +++ b/config/clusters/openscapes/common.values.yaml @@ -60,12 +60,12 @@ basehub: default: true slug: "python" kubespawner_override: - image: openscapes/python:11de2c5 + image: openscapes/python:431a94c rocker: display_name: R slug: "rocker" kubespawner_override: - image: openscapes/rocker:53180c0 + image: openscapes/rocker:b88a034 matlab: display_name: Matlab slug: "matlab" diff --git a/config/clusters/openscapes/staging.values.yaml b/config/clusters/openscapes/staging.values.yaml new file mode 100644 index 0000000000..9a2f602c97 --- /dev/null +++ b/config/clusters/openscapes/staging.values.yaml @@ -0,0 +1,132 @@ +basehub: + nfs: + pv: + # from https://docs.aws.amazon.com/efs/latest/ug/mounting-fs-nfs-mount-settings.html + mountOptions: + - rsize=1048576 + - wsize=1048576 + - timeo=600 + - soft # We pick soft over hard, so NFS lockups don't lead to hung processes + - retrans=2 + - noresvport + serverIP: fs-b25253b5.efs.us-west-2.amazonaws.com + baseShareName: / + jupyterhub: + custom: + 2i2c: + add_staff_user_ids_to_admin_users: true + add_staff_user_ids_of_type: "github" + homepage: + templateVars: + org: + name: Openscapes + logo_url: https://www.openscapes.org/img/logo.png + url: https://www.openscapes.org/ + designed_by: + name: 2i2c + url: https://2i2c.org + operated_by: + name: 2i2c + url: https://2i2c.org + funded_by: + name: Openscapes + url: https://www.openscapes.org/ + singleuser: + serviceAccountName: cloud-user-sa + defaultUrl: /lab + # User image repo: https://github.com/NASA-Openscapes/corn + image: + name: 783616723547.dkr.ecr.us-west-2.amazonaws.com/user-image + tag: "d78bb6c" + storage: + extraVolumeMounts: + - name: home + mountPath: /home/jovyan/shared + subPath: _shared + readOnly: false + profileList: + # The mem-guarantees are here so k8s doesn't schedule other pods + # on these nodes. + - display_name: "Small: m5.large" + description: "~2 CPU, ~8G RAM" + slug: "small" + default: true + profile_options: &profile_options + image: + display_name: Image + choices: + python: + display_name: Python + default: true + slug: "python" + kubespawner_override: + image: openscapes/python:11de2c5 + rocker: + display_name: R + slug: "rocker" + kubespawner_override: + image: openscapes/rocker:53180c0 + matlab: + display_name: Matlab + slug: "matlab" + kubespawner_override: + image: openscapes/matlab:fb41496 + kubespawner_override: + # Expllicitly unset mem_limit, so it overrides the default memory limit we set in + # basehub/values.yaml + mem_limit: null + mem_guarantee: 6.5G + node_selector: + node.kubernetes.io/instance-type: m5.large + - display_name: "Medium: m5.xlarge" + description: "~4 CPU, ~15G RAM" + profile_options: *profile_options + kubespawner_override: + mem_limit: null + mem_guarantee: 12G + node_selector: + node.kubernetes.io/instance-type: m5.xlarge + - display_name: "Large: m5.2xlarge" + description: "~8 CPU, ~30G RAM" + profile_options: *profile_options + kubespawner_override: + mem_limit: null + mem_guarantee: 26G + node_selector: + node.kubernetes.io/instance-type: m5.2xlarge + - display_name: "Huge: m5.8xlarge" + description: "~32 CPU, ~128G RAM" + profile_options: *profile_options + kubespawner_override: + mem_limit: null + mem_guarantee: 115G + node_selector: + node.kubernetes.io/instance-type: m5.8xlarge + scheduling: + userPlaceholder: + enabled: false + replicas: 0 + userScheduler: + enabled: false + hub: + allowNamedServers: true + networkPolicy: + # FIXME: For dask gateway + enabled: false + readinessProbe: + enabled: false + config: + Authenticator: + admin_users: &users + - amfriesz + - jules32 + - erinmr + - betolink + # Without this, any GitHub user can authenticate + allowed_users: *users +dask-gateway: + gateway: + extraConfig: + idle: |- + # timeout after 30 minutes of inactivity + c.KubeClusterConfig.idle_timeout = 1800 From 51f462be66fa5fd05eef2705ba1b827a38367665 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Wed, 21 Dec 2022 14:25:31 -0800 Subject: [PATCH 091/105] Set only profile override in openscapes staging --- config/clusters/openscapes/cluster.yaml | 1 + .../clusters/openscapes/staging.values.yaml | 72 +------------------ 2 files changed, 2 insertions(+), 71 deletions(-) diff --git a/config/clusters/openscapes/cluster.yaml b/config/clusters/openscapes/cluster.yaml index 419f100109..451c43570d 100644 --- a/config/clusters/openscapes/cluster.yaml +++ b/config/clusters/openscapes/cluster.yaml @@ -23,6 +23,7 @@ hubs: # The order in which you list files here is the order the will be passed # to the helm upgrade command in, and that has meaning. Please check # that you intend for these files to be applied in this order. + - common.values.yaml - staging.values.yaml - name: prod display_name: "Openscapes (prod)" diff --git a/config/clusters/openscapes/staging.values.yaml b/config/clusters/openscapes/staging.values.yaml index 9a2f602c97..667426b424 100644 --- a/config/clusters/openscapes/staging.values.yaml +++ b/config/clusters/openscapes/staging.values.yaml @@ -1,49 +1,7 @@ +# Overrides what is in common.values.yaml, used for temporary testing basehub: - nfs: - pv: - # from https://docs.aws.amazon.com/efs/latest/ug/mounting-fs-nfs-mount-settings.html - mountOptions: - - rsize=1048576 - - wsize=1048576 - - timeo=600 - - soft # We pick soft over hard, so NFS lockups don't lead to hung processes - - retrans=2 - - noresvport - serverIP: fs-b25253b5.efs.us-west-2.amazonaws.com - baseShareName: / jupyterhub: - custom: - 2i2c: - add_staff_user_ids_to_admin_users: true - add_staff_user_ids_of_type: "github" - homepage: - templateVars: - org: - name: Openscapes - logo_url: https://www.openscapes.org/img/logo.png - url: https://www.openscapes.org/ - designed_by: - name: 2i2c - url: https://2i2c.org - operated_by: - name: 2i2c - url: https://2i2c.org - funded_by: - name: Openscapes - url: https://www.openscapes.org/ singleuser: - serviceAccountName: cloud-user-sa - defaultUrl: /lab - # User image repo: https://github.com/NASA-Openscapes/corn - image: - name: 783616723547.dkr.ecr.us-west-2.amazonaws.com/user-image - tag: "d78bb6c" - storage: - extraVolumeMounts: - - name: home - mountPath: /home/jovyan/shared - subPath: _shared - readOnly: false profileList: # The mem-guarantees are here so k8s doesn't schedule other pods # on these nodes. @@ -102,31 +60,3 @@ basehub: mem_guarantee: 115G node_selector: node.kubernetes.io/instance-type: m5.8xlarge - scheduling: - userPlaceholder: - enabled: false - replicas: 0 - userScheduler: - enabled: false - hub: - allowNamedServers: true - networkPolicy: - # FIXME: For dask gateway - enabled: false - readinessProbe: - enabled: false - config: - Authenticator: - admin_users: &users - - amfriesz - - jules32 - - erinmr - - betolink - # Without this, any GitHub user can authenticate - allowed_users: *users -dask-gateway: - gateway: - extraConfig: - idle: |- - # timeout after 30 minutes of inactivity - c.KubeClusterConfig.idle_timeout = 1800 From 1e95b230679e24d970181c3310cc07914347dabf Mon Sep 17 00:00:00 2001 From: Julius Busecke Date: Fri, 23 Dec 2022 11:58:17 -0500 Subject: [PATCH 092/105] Add ML notebook choice to medium server We would like to be able to select the ML specific images not just for the GPU specific server, but also for the medium server. This option is available for more members and will in particular be used for an upcoming workshop. I am not sure if this simple change does the trick or if anything elsewhere needs to be specified. --- config/clusters/leap/common.values.yaml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/config/clusters/leap/common.values.yaml b/config/clusters/leap/common.values.yaml index bcda0b11b4..eb0cc08db1 100644 --- a/config/clusters/leap/common.values.yaml +++ b/config/clusters/leap/common.values.yaml @@ -83,6 +83,26 @@ basehub: allowed_teams: - leap-stc:leap-pangeo-users - 2i2c-org:tech-team + profile_options: + image: + display_name: Image + choices: + pangeo: + display_name: Base Pangeo Notebook + default: true + slug: "pangeo" + kubespawner_override: + image: "pangeo/pangeo-notebook:2022.10.18" + tensorflow: + display_name: Pangeo Tensorflow ML Notebook + slug: "tensorflow" + kubespawner_override: + image: "pangeo/ml-notebook:2022.10.18" + pytorch: + display_name: Pangeo PyTorch ML Notebook + slug: "pytorch" + kubespawner_override: + image: "pangeo/pytorch-notebook:2022.10.18" kubespawner_override: mem_limit: 15G mem_guarantee: 11G From c9461ab09f637746418092d6e1e81995a6fe927f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 Dec 2022 21:41:58 +0000 Subject: [PATCH 093/105] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/isort: v5.11.3 → 5.11.4](https://github.com/pycqa/isort/compare/v5.11.3...5.11.4) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b385d2ef38..0837ebcbdd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,7 +28,7 @@ repos: # Autoformat: Python code - repo: https://github.com/pycqa/isort - rev: "v5.11.3" + rev: "5.11.4" hooks: - id: isort From 41c7be75ca4be845a2c909b8d48f9e4850c971a6 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Mon, 2 Jan 2023 13:00:53 -0800 Subject: [PATCH 094/105] Bump utoronto R image Brings in https://github.com/2i2c-org/utoronto-r-image for https://2i2c.freshdesk.com/a/tickets/336 --- config/clusters/utoronto/r-common.values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/clusters/utoronto/r-common.values.yaml b/config/clusters/utoronto/r-common.values.yaml index 4065427b46..e2b3c1c14d 100644 --- a/config/clusters/utoronto/r-common.values.yaml +++ b/config/clusters/utoronto/r-common.values.yaml @@ -11,4 +11,4 @@ jupyterhub: defaultUrl: /rstudio image: name: quay.io/2i2c/utoronto-r-image - tag: "bd1a9c4eea2e" + tag: "cba149c9ee05" From 89bcbe81470ff7a90b1926fe35a82bacb1ba6ef3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Jan 2023 01:17:49 +0000 Subject: [PATCH 095/105] Bump rich from 12.6.0 to 13.0.0 Bumps [rich](https://github.com/Textualize/rich) from 12.6.0 to 13.0.0. - [Release notes](https://github.com/Textualize/rich/releases) - [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md) - [Commits](https://github.com/Textualize/rich/compare/v12.6.0...v13.0.0) --- updated-dependencies: - dependency-name: rich dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- dev-requirements.txt | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index f2d7ab9114..f86b270f1f 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -10,4 +10,4 @@ chartpress==2.1.0 requests==2.28.1 # rich is used by extra_scripts/count-auth0-apps.py -rich==12.6.0 +rich==13.0.0 diff --git a/requirements.txt b/requirements.txt index 9df7400fcb..be2de5d76c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,7 +14,7 @@ jsonschema==4.17.0 # rich and py-markdown-table are used for pretty printing outputs that would otherwise # be difficult to parse by a human -rich==12.6.0 +rich==13.0.0 py-markdown-table==0.3.1 # jhub_client, pytest, and pytest-asyncio are used for our health checks From 09325611a86400e656ffb66da82280758535bdb6 Mon Sep 17 00:00:00 2001 From: Julius Busecke Date: Wed, 4 Jan 2023 10:38:21 -0500 Subject: [PATCH 096/105] Bump to recent pangeo image version --- config/clusters/leap/common.values.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/config/clusters/leap/common.values.yaml b/config/clusters/leap/common.values.yaml index eb0cc08db1..fa077f3a7e 100644 --- a/config/clusters/leap/common.values.yaml +++ b/config/clusters/leap/common.values.yaml @@ -55,7 +55,7 @@ basehub: singleuser: image: name: pangeo/pangeo-notebook - tag: "2022.10.18" + tag: "2023.01.03" extraEnv: GH_SCOPED_CREDS_CLIENT_ID: "Iv1.0c7df3d4b3191b2f" GH_SCOPED_CREDS_APP_URL: https://github.com/apps/leap-hub-push-access @@ -92,17 +92,17 @@ basehub: default: true slug: "pangeo" kubespawner_override: - image: "pangeo/pangeo-notebook:2022.10.18" + image: "pangeo/pangeo-notebook:2023.01.03" tensorflow: display_name: Pangeo Tensorflow ML Notebook slug: "tensorflow" kubespawner_override: - image: "pangeo/ml-notebook:2022.10.18" + image: "pangeo/ml-notebook:2023.01.03" pytorch: display_name: Pangeo PyTorch ML Notebook slug: "pytorch" kubespawner_override: - image: "pangeo/pytorch-notebook:2022.10.18" + image: "pangeo/pytorch-notebook:2023.01.03" kubespawner_override: mem_limit: 15G mem_guarantee: 11G @@ -144,12 +144,12 @@ basehub: default: true slug: "tensorflow" kubespawner_override: - image: "pangeo/ml-notebook:2022.10.18" + image: "pangeo/ml-notebook:2023.01.03" pytorch: display_name: Pangeo PyTorch ML Notebook slug: "pytorch" kubespawner_override: - image: "pangeo/pytorch-notebook:2022.10.18" + image: "pangeo/pytorch-notebook:2023.01.03" kubespawner_override: node_selector: cloud.google.com/gke-nodepool: nb-gpu-t4 From af8b689a115d141f482f1bee6cab12d2943f8d35 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Fri, 18 Nov 2022 14:17:21 -0800 Subject: [PATCH 097/105] Bump version of dask-gateway There are new changes here that will help a lot with memory usage! See https://gateway.dask.org/changelog.html#id1 --- deployer/deployer.py | 2 +- helm-charts/daskhub/Chart.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/deployer/deployer.py b/deployer/deployer.py index 5ec79fea4a..4758f4f761 100644 --- a/deployer/deployer.py +++ b/deployer/deployer.py @@ -199,7 +199,7 @@ def deploy( help="File to read secret deployment config from", ), dask_gateway_version: str = typer.Option( - "2022.10.0", help="Version of dask-gateway to install CRDs for" + "2022.11.0", help="Version of dask-gateway to install CRDs for" ), ): """ diff --git a/helm-charts/daskhub/Chart.yaml b/helm-charts/daskhub/Chart.yaml index fd104fbdbc..05d4e1a322 100644 --- a/helm-charts/daskhub/Chart.yaml +++ b/helm-charts/daskhub/Chart.yaml @@ -9,7 +9,7 @@ dependencies: repository: file://../basehub # If bumping the version of dask-gateway, please also bump the default version set # in the deployer's CLI - # https://github.com/2i2c-org/infrastructure/blob/HEAD/deployer/cli.py#L73-L80 + # https://github.com/2i2c-org/infrastructure/blob/HEAD/deployer/deployer.py#L195 - name: dask-gateway - version: "2022.10.0" + version: "2022.11.0" repository: "https://helm.dask.org/" From 8af5414e941db0ca5563c161cdb84b8e299023da Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 4 Jan 2023 17:45:25 +0100 Subject: [PATCH 098/105] Update dask-gateway to 2023.1.0 --- deployer/deployer.py | 2 +- helm-charts/daskhub/Chart.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deployer/deployer.py b/deployer/deployer.py index 4758f4f761..d9a69e9cab 100644 --- a/deployer/deployer.py +++ b/deployer/deployer.py @@ -199,7 +199,7 @@ def deploy( help="File to read secret deployment config from", ), dask_gateway_version: str = typer.Option( - "2022.11.0", help="Version of dask-gateway to install CRDs for" + "2023.1.0", help="Version of dask-gateway to install CRDs for" ), ): """ diff --git a/helm-charts/daskhub/Chart.yaml b/helm-charts/daskhub/Chart.yaml index 05d4e1a322..41316307d0 100644 --- a/helm-charts/daskhub/Chart.yaml +++ b/helm-charts/daskhub/Chart.yaml @@ -11,5 +11,5 @@ dependencies: # in the deployer's CLI # https://github.com/2i2c-org/infrastructure/blob/HEAD/deployer/deployer.py#L195 - name: dask-gateway - version: "2022.11.0" + version: "2023.1.0" repository: "https://helm.dask.org/" From 628261713fa307f1ced7b23d1dedeb524a734503 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Wed, 4 Jan 2023 12:04:52 -0800 Subject: [PATCH 099/105] Bump version of sops pre-commit hook Brings in fix to allow opening .json files that have hard tabs --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0837ebcbdd..86d606a187 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -46,7 +46,7 @@ repos: # Prevent unencrypted files from being committed - repo: https://github.com/yuvipanda/pre-commit-hook-ensure-sops - rev: v1.0 + rev: v1.1 hooks: - id: sops-encryption # Add files here if they contain the word 'secret' but should not be encrypted From a9c14b210892b12acbb31b0c4af96c9f49cf5d84 Mon Sep 17 00:00:00 2001 From: Yuvi Panda Date: Mon, 12 Dec 2022 21:47:56 -0800 Subject: [PATCH 100/105] Revert "Set utoronto staging hub limits to match exam limits" --- config/clusters/utoronto/default-staging.values.yaml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/config/clusters/utoronto/default-staging.values.yaml b/config/clusters/utoronto/default-staging.values.yaml index 7bb2413f98..c14a353af1 100644 --- a/config/clusters/utoronto/default-staging.values.yaml +++ b/config/clusters/utoronto/default-staging.values.yaml @@ -1,13 +1,4 @@ jupyterhub: - # superseeded by https://github.com/2i2c-org/infrastructure/pull/1962/ - singleuser: - memory: - # Ensure all students get equal resources during the exam - limit: 2G - guarantee: 2G - cpu: - limit: 1 - guarantee: 1 hub: config: AzureAdOAuthenticator: From 9fa07bad2c94a659778fabf6a33308de8b4a4b7d Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Wed, 4 Jan 2023 16:10:21 -0800 Subject: [PATCH 101/105] Enable continuous prepuller for leap Ref https://2i2c.freshdesk.com/a/tickets/349 --- config/clusters/leap/common.values.yaml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/config/clusters/leap/common.values.yaml b/config/clusters/leap/common.values.yaml index fa077f3a7e..c1f62d6293 100644 --- a/config/clusters/leap/common.values.yaml +++ b/config/clusters/leap/common.values.yaml @@ -10,9 +10,14 @@ basehub: # Name of Google Filestore share baseShareName: /homes/ jupyterhub: - proxy: - https: - enabled: false + prePuller: + continuous: + enabled: true + # Extra images to be pulled on all nodes + extraImages: + tensorflow-image: + name: pangeo/ml-notebook + tag: "2023.01.03" custom: 2i2c: add_staff_user_ids_to_admin_users: true From 596a8aa5c12d5264f005dd82391de46caf190e65 Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Thu, 5 Jan 2023 15:20:18 +0000 Subject: [PATCH 102/105] Switch staging hub auth to github --- config/clusters/2i2c-aws-us/cluster.yaml | 3 ++- .../enc-staging.secret.values.yaml | 20 +++++++++++++++++++ .../clusters/2i2c-aws-us/staging.values.yaml | 12 ++++++++++- 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 config/clusters/2i2c-aws-us/enc-staging.secret.values.yaml diff --git a/config/clusters/2i2c-aws-us/cluster.yaml b/config/clusters/2i2c-aws-us/cluster.yaml index a862144e61..164c583a09 100644 --- a/config/clusters/2i2c-aws-us/cluster.yaml +++ b/config/clusters/2i2c-aws-us/cluster.yaml @@ -15,9 +15,10 @@ hubs: domain: staging.aws.2i2c.cloud helm_chart: basehub auth0: - connection: google-oauth2 + enabled: false helm_chart_values_files: - staging.values.yaml + - enc-staging.secret.values.yaml - name: dask-staging display_name: "2i2c AWS dask-staging" domain: dask-staging.aws.2i2c.cloud diff --git a/config/clusters/2i2c-aws-us/enc-staging.secret.values.yaml b/config/clusters/2i2c-aws-us/enc-staging.secret.values.yaml new file mode 100644 index 0000000000..4d81acbd31 --- /dev/null +++ b/config/clusters/2i2c-aws-us/enc-staging.secret.values.yaml @@ -0,0 +1,20 @@ +jupyterhub: + hub: + config: + GitHubOAuthenticator: + client_id: ENC[AES256_GCM,data:cCcFsREidFJE+8mNguzsiglaDNU=,iv:8/ONoisn8fresVPcztUejV5j1njBJe1TiyTPbzsl9ag=,tag:1zUsouNBoTdAAoVwZCrXNw==,type:str] + client_secret: ENC[AES256_GCM,data:QVmzgFYEGKPgTrj6bCrumcQp5mZreLFrw6G+zITLOSJtydDY62zD/Q==,iv:nAdsoPGHyPhFzFPU4uxRAbQOm4fuZSD4IKtZYAHx3vU=,tag:jV9OsG1sy/OyOTUp7M9pvA==,type:str] +sops: + kms: [] + gcp_kms: + - resource_id: projects/two-eye-two-see/locations/global/keyRings/sops-keys/cryptoKeys/similar-hubs + created_at: "2023-01-05T15:13:06Z" + enc: CiUA4OM7ePFTK8jhF1PvaNsOlsH2PwopRPJE7K+2pVikF/Brl4zPEkkA+0T9hUjgsZa2zAOVqZHOAjeg91553kP+YnHnIW2QPPnSha1dlGfrTcesxV5hsrbeqc3fxs7OnKow5KK3fr48Djf31CYGOgLY + azure_kv: [] + hc_vault: [] + age: [] + lastmodified: "2023-01-05T15:13:06Z" + mac: ENC[AES256_GCM,data:tSbbwxnJEQflFUma0Ou0Faci4nypPH7UDFkPe6jyfnNEckteG9qviBk9pUbpuR404NOZp8QtVqB0Vqv3fwo1EBGrmeT0E0zhgz0RR2/k85nDnPD9mLYWq4xtestHQOdibmMAFe4D2QbVSgI4t8NiYAWLPqKaH8UGKFgiFAV9pnU=,iv:yPX35eIVYw1rh9BTmPpc3P+DzEu499Wk/GJkwN4AcUs=,tag:mWr1ieAR7bZ605gA2tHHdA==,type:str] + pgp: [] + unencrypted_suffix: _unencrypted + version: 3.7.3 diff --git a/config/clusters/2i2c-aws-us/staging.values.yaml b/config/clusters/2i2c-aws-us/staging.values.yaml index 21d190a358..7cd73a816c 100644 --- a/config/clusters/2i2c-aws-us/staging.values.yaml +++ b/config/clusters/2i2c-aws-us/staging.values.yaml @@ -17,7 +17,7 @@ jupyterhub: custom: 2i2c: add_staff_user_ids_to_admin_users: true - add_staff_user_ids_of_type: "google" + add_staff_user_ids_of_type: "github" homepage: templateVars: org: @@ -33,3 +33,13 @@ jupyterhub: funded_by: name: 2i2c url: https://2i2c.org + hub: + config: + JupyterHub: + authenticator_class: "github" + GitHubOAuthenticator: + oauth_callback_url: "https://staging.aws.2i2c.cloud/hub/oauth_callback" + allowed_organizations: + - 2i2c-org + scope: + - read:org From 4f5afa1a6bd6f97a28bd86e71eeee8ded1c232d0 Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Thu, 5 Jan 2023 15:32:54 +0000 Subject: [PATCH 103/105] Switch dask-staging hub auth to github --- .../2i2c-aws-us/dask-staging.values.yaml | 19 +++++++------------ .../enc-dask-staging.secret.values.yaml | 14 +++++++------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/config/clusters/2i2c-aws-us/dask-staging.values.yaml b/config/clusters/2i2c-aws-us/dask-staging.values.yaml index d7b4939e9f..9a643b24d8 100644 --- a/config/clusters/2i2c-aws-us/dask-staging.values.yaml +++ b/config/clusters/2i2c-aws-us/dask-staging.values.yaml @@ -18,7 +18,7 @@ basehub: custom: 2i2c: add_staff_user_ids_to_admin_users: true - add_staff_user_ids_of_type: "google" + add_staff_user_ids_of_type: "github" homepage: templateVars: org: @@ -41,15 +41,10 @@ basehub: hub: config: JupyterHub: - authenticator_class: cilogon - CILogonOAuthenticator: + authenticator_class: github + GitHubOAuthenticator: oauth_callback_url: "https://dask-staging.aws.2i2c.cloud/hub/oauth_callback" - # Only show the option to login with Google - shown_idps: - - https://accounts.google.com/o/oauth2/auth - allowed_idps: - http://google.com/accounts/o8/id: - username_derivation: - username_claim: "email" - allowed_domains: - - "2i2c.org" + allowed_organizations: + - 2i2c-org + scope: + - read:org diff --git a/config/clusters/2i2c-aws-us/enc-dask-staging.secret.values.yaml b/config/clusters/2i2c-aws-us/enc-dask-staging.secret.values.yaml index eb8096b3f0..34035cf309 100644 --- a/config/clusters/2i2c-aws-us/enc-dask-staging.secret.values.yaml +++ b/config/clusters/2i2c-aws-us/enc-dask-staging.secret.values.yaml @@ -2,20 +2,20 @@ basehub: jupyterhub: hub: config: - CILogonOAuthenticator: - client_id: ENC[AES256_GCM,data:BfiiGt1HfLSDODMfquDhqMomaEUaHwkqeP26O36yf+jtG5Ygz65/7HGmXXFLUQfAOLrr,iv:0pQMqJEMRB5gG6rLFkEC3wQ4gIIPZGqHddc99nHZmBg=,tag:MoQTbBW7R46WfIpnkCvtLw==,type:str] - client_secret: ENC[AES256_GCM,data:0iXNqbfl/VyjCrdlrqxi4pseJo8kU5OjTbBrsGtGq94Hu+66e7I0F0PrDEIaMhnJeeLflTC2wIjdyDeRMf7Q0BNN96QVXShiNH/P/XkSqB6Ib5PDjJ8=,iv:cOxuNCoEJr5yyEO7CLD8ImRIHTYghIWryL//cw9fknM=,tag:HkItVgFlGI4y+WEFpt7hqw==,type:str] + GitHubOAuthenticator: + client_id: ENC[AES256_GCM,data:ZiUCM3p/hY+y/zpQFS+R13pd7wY=,iv:M3N7NmEtE5qngrCwlr2vLxTUaFvbQM+q6uOGsw3Vmbg=,tag:TtN0bMLKmSiSSiPipWCEAw==,type:str] + client_secret: ENC[AES256_GCM,data:46JoY+yo01tG12QsaIlpuZ9hX00YfHHmL+MXKkyl4ncHQjVbj+4hNQ==,iv:T1YQnD2mr2JKhAtSHaPXTTlD8CreiHOxAxG1wfueNME=,tag:s0xmzKuhqUKaWy6pmJzQUw==,type:str] sops: kms: [] gcp_kms: - resource_id: projects/two-eye-two-see/locations/global/keyRings/sops-keys/cryptoKeys/similar-hubs - created_at: "2022-12-05T10:44:39Z" - enc: CiUA4OM7eJ8AFQjo7QEG0fUwXYqcHq9+9DAN0AuGSbzM7+5lmVnhEkkA+0T9hRclOu/WCURrpLEl8kNNUT8G5Nz0bARjq5Sji4gbyWzDcryqOJA4WewJ3dDFRRtNUP5febFlyA+uh99+RRanK3FDyjEz + created_at: "2023-01-05T15:25:09Z" + enc: CiUA4OM7eN6ULedt07hrYSuCYNviz84p6Myz4gLx0SHi2soZ8b5JEkkA+0T9hZ0nLyBqO0b1X7/wXW+AXp9K52uffuEDJvyCG97WP75nEi2k0QqjCsLQGaIr7QuYzkVkvMWf6bfJeLE8DravcpVoVqUH azure_kv: [] hc_vault: [] age: [] - lastmodified: "2022-12-05T10:44:40Z" - mac: ENC[AES256_GCM,data:FSgZ25Taj9528GUOhHMHRvrNTa7NHTngGnA3fyInZvLYxrD5JEpcEsfhOMYqenLKF5BfvN6jEWhORmL+baCNiz7luWgDuQQPsRZgZcmvj8H9lgWSn/xXcTrzRidWiMfYbDMDEXCJX34vd4seaPAxRfIlgv4ObeHjZYLI9ATWmDA=,iv:MpIuzaHsue858Qs8OMk0iwE/w1J5e28gg88mQpG6GvU=,tag:u/z+s6x8xVW5L2RjhMVpjA==,type:str] + lastmodified: "2023-01-05T15:25:09Z" + mac: ENC[AES256_GCM,data:4rpy/3DM8UDSnyeB+B2JLWE/xaucbCvwucTv3cU5rItFZaUIlmynLcXpqhOBCoEEfPbAFG+eTo3Kdm8qEETFl4Ssd8yNU9kedgOn8V64LT/hTWaPzmGkzTx3bdjXIjHJwuvLpMItSonItapwSbN4ZOSdRUETylzpxr+zYhJySvw=,iv:PP+vA0UrQmlCXMwVzzWrIT+fF9TvZxad4VXLA16EsUw=,tag:D3qv+InhBs6Najw0E7gIXA==,type:str] pgp: [] unencrypted_suffix: _unencrypted version: 3.7.3 From 2d736370c0a7549e2b92a7c4d940c1cc867f6e86 Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Thu, 5 Jan 2023 15:58:47 +0000 Subject: [PATCH 104/105] Define blank list of admin_users and comment explaining why This restores admin access to 2i2c staff members --- config/clusters/2i2c-aws-us/dask-staging.values.yaml | 9 +++++++++ config/clusters/2i2c-aws-us/staging.values.yaml | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/config/clusters/2i2c-aws-us/dask-staging.values.yaml b/config/clusters/2i2c-aws-us/dask-staging.values.yaml index 9a643b24d8..5d25512f7f 100644 --- a/config/clusters/2i2c-aws-us/dask-staging.values.yaml +++ b/config/clusters/2i2c-aws-us/dask-staging.values.yaml @@ -40,6 +40,15 @@ basehub: tag: "2022.06.02" hub: config: + Authenticator: + # This hub uses GitHub Org auth and so we don't set + # allowed_users in order to not deny access to valid members of + # the listed orgs. + # + # You must always set admin_users, even if it is an empty list, + # otherwise `add_staff_user_ids_to_admin_users: true` will fail + # silently and no staff members will have admin access. + admin_users: [] JupyterHub: authenticator_class: github GitHubOAuthenticator: diff --git a/config/clusters/2i2c-aws-us/staging.values.yaml b/config/clusters/2i2c-aws-us/staging.values.yaml index 7cd73a816c..a6fed752cf 100644 --- a/config/clusters/2i2c-aws-us/staging.values.yaml +++ b/config/clusters/2i2c-aws-us/staging.values.yaml @@ -35,6 +35,15 @@ jupyterhub: url: https://2i2c.org hub: config: + Authenticator: + # This hub uses GitHub Org auth and so we don't set + # allowed_users in order to not deny access to valid members of + # the listed orgs. + # + # You must always set admin_users, even if it is an empty list, + # otherwise `add_staff_user_ids_to_admin_users: true` will fail + # silently and no staff members will have admin access. + admin_users: [] JupyterHub: authenticator_class: "github" GitHubOAuthenticator: From 69e6b03f21c7b68ba74b8087adfdb0ff0f057a6e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 5 Jan 2023 16:00:24 +0000 Subject: [PATCH 105/105] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- config/clusters/2i2c-aws-us/dask-staging.values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/clusters/2i2c-aws-us/dask-staging.values.yaml b/config/clusters/2i2c-aws-us/dask-staging.values.yaml index 5d25512f7f..55354fbb34 100644 --- a/config/clusters/2i2c-aws-us/dask-staging.values.yaml +++ b/config/clusters/2i2c-aws-us/dask-staging.values.yaml @@ -56,4 +56,4 @@ basehub: allowed_organizations: - 2i2c-org scope: - - read:org + - read:org