From a3fac3eaf14d8d396a7754f54d8fe42e37bbcfed Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Thu, 26 Oct 2023 11:56:10 -0400 Subject: [PATCH 01/18] wip schema --- schema/example.json | 107 +++++ schema/posit-publishing-schema-v3.json | 522 +++++++++++++++++++++++++ 2 files changed, 629 insertions(+) create mode 100644 schema/example.json create mode 100644 schema/posit-publishing-schema-v3.json diff --git a/schema/example.json b/schema/example.json new file mode 100644 index 000000000..2e79b47fd --- /dev/null +++ b/schema/example.json @@ -0,0 +1,107 @@ +{ + // URL to the schema definition, which we should make public. + "$schema": "./posit-publishing-schema-v3.json", + "type": "quarto-static", + "entrypoint": "report.qmd", + + // name or id can be used to specify a target deployment to update. + // name can be used for initial deployment as well as updates. + // id can only be used for updates once the content has been deployed. + "name": "quarterly-sales-report-by-region", + "id": "de2e7bdb-b085-401e-a65c-443e40009749", + + // Is this Connect-specific? + "custom_url": "/reports/quarterly-sales/", + + // These metadata fields are purely for human consumption. + "title": "Regional Quarterly Sales Report", + "description": "This is the quarterly sales report, broken down by region.", + "author": { + "name": "Michael Marchetti", + "email": "mike@posit.co", + "username": "mmarchetti", + "organization": "Posit, PBC" + }, + // Arbitrary string tags. On Connect, only admins can create tags, + // and tags can be nested (e.g. "sales,reports,quarterly" is a + // single tag 3 levels deep in the tag tree). The example below + // is 3 separate top level tags. + "tags": [ + "sales", + "quarterly", + "regional" + ], + // List of (relative) file paths. If constructing a bundle to upload, + // these files must be included. For git deployments, the server + // will expect (required) that these files be present in the repo. + // Unlike manifest v1, there are no checksums, since those require + // updating this file whenever other files change, and we'd prefer + // to write this once and commit it. + "files": [ + "report.qmd", + "_quarto.yml", + "model.py", + "requirements.txt", + "weights/*.hdf5" + ], + "dependencies": { + "python": { + "version": "3.11.3", + "package_file": "requirements.txt", + "package_manager": "pip" + }, + "r": { + "version": "4.3.1", + "package_file": "renv.lock", + "package_manager": "packrat" + }, + "quarto": { + "version": "1.4" + } + }, + "environment": { + "API_URL": "https://example.com/api", + // secret value, will be pulled from the environment when deploying through CLI, + // must be set at the server if deploying through git. + "API_KEY": null + }, + "schedule": { + // Only valid for reports, not for apps. + // iCalendar spec: https://icalendar.org/iCalendar-RFC-5545/3-8-5-3-recurrence-rule.html + "start": "2023-10-25T08:00:00Z", + "recurrence": "FREQ=MONTHLY;INTERVAL=3" + }, + "connect": { + "access": { + "type": "logged-in", + "run_as": "rstudio-connect", + "run_as_current_user": false + }, + "runtime": { + // Might some of these apply beyond Connect? For example, init_timeout + // tells the server how long this app might take to start up. + "connection_timeout": 5, + "read_timeout": 30, + "init_timeout": 60, + "idle_timeout": 120, + "max_processes": 5, + "min_processes": 1, + "max_conns_per_process": 50, + "load_factor": 0.5 + }, + "kubernetes": { + // Will Posit Cloud Connect be containerized? If so, will it support + // image_name, cpu_limit, memory_limit, and/or gpu settings? + "amd_gpu_limit": 0, + "cpu_limit": 1, + "cpu_request": 0.5, + "image_name": "posit/connect-runtime-python3.11-r4.3", + "memory_limit": "100000000", + "memory_request": "20000000", + "nvidia_gpu_limit": 0, + "service_account_name": "k8s-runner", + "r_environment_management": true, + "py_environment_management": true + } + } +} diff --git a/schema/posit-publishing-schema-v3.json b/schema/posit-publishing-schema-v3.json new file mode 100644 index 000000000..44862f0ed --- /dev/null +++ b/schema/posit-publishing-schema-v3.json @@ -0,0 +1,522 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/rstudio/publishing-client/posit-publishing-schema-v3.json", + "type": "object", + "additionalProperties": false, + "default": {}, + "description": "Posit Deployment", + "required": [ + "$schema", + "type", + "entrypoint", + "files", + "dependencies" + ], + "properties": { + "$schema": { + "type": "string", + "format": "url", + "description": "URL of the json-schema definition for this file. Must be 'https://cdn.posit.co/connect/posit-publishing-schema-v3.json'.", + "enum": ["./posit-publishing-schema-v3.json"], + "examples": [ + "./posit-publishing-schema-v3.json" + ] + }, + "type": { + "type": "string", + "description": "Indicates the type of content being deployed.", + "enum": [ + "api", + "jupyter-static", + "jupyter-voila", + "python-api", + "python-bokeh", + "python-dash", + "python-fastapi", + "python-shiny", + "python-streamlit", + "quarto-shiny", + "quarto-static", + "rmd-shiny", + "rmd-static", + "shiny", + "static", + "tensorflow-saved-model" + ], + "examples": [ + "quarto-static" + ] + }, + "entrypoint": { + "type": "string", + "description": "Name of the primary file containing the content. For some types of Python executable content, this may also indicate the object within the file in module:object format.", + "examples": [ + "app.py", + "report.qmd" + ] + }, + "name": { + "type": "string", + "pattern": "^[a-zA-Z0-9_-]{3,64}$|", + "default": "", + "description": "Unique name for this deployment. If present, and a deployment with this name exists, it will be updated, otherwise it will be created.", + "examples": [ + "quarterly-sales-report-by-region" + ] + }, + "id": { + "type": "string", + "default": null, + "description": "Unique ID of this deployment. If present, it must be the ID of an existing deployment of the same type on the server, which will be updated.", + "examples": [ + "de2e7bdb-b085-401e-a65c-443e40009749" + ] + }, + "custom_url": { + "type": "string", + "pattern": "^/[/a-zA-Z0-9-_]+/$", + "default": null, + "description": "Custom URL path to assign to this content. The URL path must be unassigned, or assigned to content you own. You must be an administrator to set this value.", + "examples": [ + "/reports/quarterly-sales" + ] + }, + "title": { + "type": "string", + "pattern": "^[^\t\n\f\r]{3,1024}$|", + "default": "", + "description": "Human-readable title for this content.", + "examples": [ + "Quarterly Sales Report" + ] + }, + "description": { + "type": "string", + "default": "", + "description": "Human-readable description for this content.", + "examples": [ + "This is the quarterly sales report, broken down by region." + ] + }, + "author": { + "type": "object", + "additionalProperties": false, + "default": null, + "description": "Information about the author of this content.", + "properties": { + "name": { + "type": "string", + "default": "", + "description": "Author name.", + "examples": [ + "Michael Marchetti" + ] + }, + "email": { + "type": "string", + "default": "", + "description": "Author email address.", + "examples": [ + "mike@posit.co" + ] + }, + "username": { + "type": "string", + "default": "", + "description": "Author's username on the publishing server.", + "examples": [ + "mmarchetti" + ] + }, + "organization": { + "type": "string", + "default": "", + "description": "Author's organization.", + "examples": [ + "Posit, PBC" + ] + } + } + }, + "tags": { + "type": "array", + "default": [], + "description": "The tags Schema", + "items": { + "type": "string", + "description": "List of tags to apply to this deployment. When publishing to Connect, tags must be pre-defined by an administrator.", + "examples": [ + "sales", + "quarterly", + "regional" + ] + } + }, + "files": { + "type": "array", + "description": "The files Schema", + "items": { + "type": "string", + "description": "List of relative file paths that are used by this deployment. When creating a bundle, these files must exist in the project directory. When deploying from git, these files must exist in the repository.", + "examples": [ + "report.qmd", + "_quarto.yml", + "model.py", + "requirements.txt", + "weights/*.hdf5" + ] + } + }, + "dependencies": { + "type": "object", + "additionalProperties": false, + "description": "Language runtimes and packages needed to run this content.", + "properties": { + "python": { + "type": "object", + "additionalProperties": false, + "default": null, + "description": "Python language and dependencies.", + "required": [ + "version" + ], + "properties": { + "version": { + "type": "string", + "description": "Python version. The server must have a similar Python version in order to run the content.", + "examples": [ + "3.11.3" + ] + }, + "package_file": { + "type": "string", + "description": "File containing package dependencies. The file must exist and be listed under 'files'.", + "default": "requirements.txt", + "examples": [ + "requirements.txt" + ] + }, + "package_manager": { + "type": "string", + "default": "pip", + "enum": [ + "pip", + "none" + ], + "description": "Package manager that will install the dependencies. If package_manager is none, dependencies will not be installed.", + "examples": [ + "pip" + ] + } + } + }, + "r": { + "type": "object", + "additionalProperties": false, + "default": null, + "description": "R language and dependencies.", + "required": [ + "version" + ], + "properties": { + "version": { + "type": "string", + "description": "R version. The server must have a similar R version in order to run the content.", + "examples": [ + "4.3.1" + ] + }, + "package_file": { + "type": "string", + "default": "renv.lock", + "description": "File containing package dependencies. The file must exist and be listed under 'files'.", + "examples": [ + "renv.lock" + ] + }, + "package_manager": { + "type": "string", + "default": "packrat", + "enum": [ + "packrat", + "none" + ], + "description": "Package manager that will install the dependencies. If package_manager is none, dependencies will not be installed.", + "examples": [ + "packrat" + ] + } + } + }, + "quarto": { + "type": "object", + "additionalProperties": false, + "default": null, + "description": "Quarto version required to run the content.", + "required": [ + "version" + ], + "properties": { + "version": { + "type": "string", + "description": "Quarto version. The server must have a similar Quarto version in order to run the content.", + "examples": [ + "1.4" + ] + } + } + } + } + }, + "environment": { + "type": "object", + "additionalProperties": true, + "default": null, + "description": "Environment variable/value map. Null values may be used for secrets; deployment tools should populate these from the deploying environment. For git-based deployments, the publishing server must be configured to provide those values.", + "examples": [ + { + "API_URL": "https://example.com/api", + "API_KEY": null + } + ] + }, + "schedule": { + "type": "object", + "additionalProperties": false, + "default": null, + "description": "Schedule for recurring execution of this content. Only applies to reports, such as Quarto and R Markdown.", + "required": [ + "start", + "recurrence" + ], + "properties": { + "start": { + "type": "string", + "description": "Time for the first run of the content.", + "examples": [ + "2023-10-25T08:00:00Z" + ] + }, + "recurrence": { + "type": "string", + "description": "Recurrence scheme for the content, in iCalendar RRULE format.", + "examples": [ + "FREQ=MONTHLY;INTERVAL=3" + ] + } + } + }, + "connect": { + "type": "object", + "additionalProperties": false, + "default": null, + "description": "Setting specific to Posit Connect deployments.", + "properties": { + "access": { + "type": "object", + "additionalProperties": false, + "default": null, + "description": "Access control settings.", + "properties": { + "type": { + "type": "string", + "default": "acl", + "description": "Type of access control. 'all' make the deployment public, with no login required. 'logged-in' allows all logged-in users to access the content. 'acl' allows only specific users to access the content.", + "enum": [ + "all", + "logged-in", + "acl" + ], + "examples": [ + "logged-in" + ] + }, + "run_as": { + "type": "string", + "default": "", + "description": "The system username under which the content should be run. Must be an existing user in the allowed group. You must be an administrator to set this value.", + "examples": [ + "rstudio-connect" + ] + }, + "run_as_current_user": { + "type": "boolean", + "default": false, + "description": "For application content types, run a separate process for each visiting user under that user's server account. Requires PAM authentication on the Posit Connect server. You must be an administrator to set this value.", + "examples": [ + false + ] + } + } + }, + "runtime": { + "type": "object", + "additionalProperties": false, + "default": null, + "description": "Runtime settings for application content types.", + "properties": { + "connection_timeout": { + "type": "integer", + "default": null, + "minimum": 0, + "description": "Maximum number of seconds allowed without data sent or received across a client connection. A value of `0` means connections will never time-out (not recommended).", + "examples": [ + 5 + ] + }, + "read_timeout": { + "type": "integer", + "default": null, + "minimum": 0, + "description": "Maximum number of seconds allowed without data received from a client connection. A value of `0` means a lack of client (browser) interaction never causes the connection to close.", + "examples": [ + 30 + ] + }, + "init_timeout": { + "type": "integer", + "default": null, + "description": "The maximum number of seconds allowed for an interactive application to start. Posit Connect must be able to connect to a newly launched application before this threshold has elapsed.", + "examples": [ + 60 + ] + }, + "idle_timeout": { + "type": "integer", + "default": null, + "description": "The maximum number of seconds a worker process for an interactive application to remain alive after it goes idle (no active connections).", + "examples": [ + 120 + ] + }, + "max_processes": { + "type": "integer", + "default": null, + "minimum": 1, + "description": "Specifies the total number of concurrent processes allowed for a single interactive application.", + "examples": [ + 5 + ] + }, + "min_processes": { + "type": "integer", + "default": null, + "minimum": 0, + "description": "Specifies the minimum number of concurrent processes allowed for a single interactive application.", + "examples": [ + 1 + ] + }, + "max_conns_per_process": { + "type": "integer", + "default": null, + "minimum": 1, + "description": "Specifies the maximum number of client connections allowed to an individual process. Incoming connections which will exceed this limit are routed to a new process or rejected.", + "examples": [ + 50 + ] + }, + "load_factor": { + "type": "number", + "default": null, + "minimum": 0, + "maximum": 1, + "description": "Controls how aggressively new processes are spawned. The valid range is between 0.0 and 1.0.", + "examples": [ + 0.5 + ] + } + } + }, + "kubernetes": { + "type": "object", + "additionalProperties": false, + "default": null, + "description": "Settings used with Posit Connect's off-host execution feature, where content is run in Kubernetes.", + "properties": { + "amd_gpu_limit": { + "type": "integer", + "default": null, + "description": "The number of AMD GPUs that will be allocated by Kubernetes to run this content.", + "examples": [ + 0 + ] + }, + "cpu_limit": { + "type": "integer", + "default": null, + "description": "The maximum amount of compute power this content will be allowed to consume when executing or rendering, expressed in CPU Units, where 1.0 unit is equivalent to 1 physical or virtual core. Fractional values are allowed. If the process tries to use more CPU than allowed, it will be throttled.", + "examples": [ + 1 + ] + }, + "cpu_request": { + "type": "number", + "default": null, + "description": "The minimum amount of compute power this content needs when executing virtual core. Fractional values are allowed.", + "examples": [ + 0.5 + ] + }, + "image_name": { + "type": "string", + "default": "", + "description": "Name of the target container image.", + "examples": [ + "posit/connect-runtime-python3.11-r4.3" + ] + }, + "memory_limit": { + "type": "string", + "default": "", + "description": "The maximum amount of RAM this content will be allowed to consume when executing or rendering, expressed in bytes. If the process tries to use more memory than allowed, it will be terminated", + "examples": [ + "100000000" + ] + }, + "memory_request": { + "type": "string", + "default": "", + "description": "The minimum amount of RAM this content needs when executing or rendering, expressed in bytes.", + "examples": [ + "20000000" + ] + }, + "nvidia_gpu_limit": { + "type": "integer", + "default": null, + "description": "The number of NVIDIA GPUs that will be allocated by Kubernetes to run this content.", + "examples": [ + 0 + ] + }, + "service_account_name": { + "type": "string", + "default": null, + "description": "The name of the Kubernetes service account that is used to run this content. It must adhere to Kubernetes service account naming rules. You must be an administrator to set this value.", + "examples": [ + "posit-connect-content" + ] + }, + "r_environment_management": { + "type": "boolean", + "default": false, + "description": "Enables or disables R environment management. When false, Posit Connect will not install R packages and instead assume that all required packages are present in the container image.", + "examples": [ + true + ] + }, + "py_environment_management": { + "type": "boolean", + "default": false, + "description": "Enables or disables Python environment management. When false, Posit Connect will not install Python packages and instead assume that all required packages are present in the container image.", + "examples": [ + true + ] + } + } + } + } + } + } +} From 1397a039124ceb7f546c6b093acc61150ecfee66 Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Mon, 30 Oct 2023 11:15:20 -0400 Subject: [PATCH 02/18] schema updates --- schema/example.json | 30 +++++++++----- schema/posit-publishing-schema-v3.json | 54 ++++++++++++++++---------- 2 files changed, 54 insertions(+), 30 deletions(-) diff --git a/schema/example.json b/schema/example.json index 2e79b47fd..b6326fbc4 100644 --- a/schema/example.json +++ b/schema/example.json @@ -4,14 +4,24 @@ "type": "quarto-static", "entrypoint": "report.qmd", - // name or id can be used to specify a target deployment to update. - // name can be used for initial deployment as well as updates. - // id can only be used for updates once the content has been deployed. - "name": "quarterly-sales-report-by-region", - "id": "de2e7bdb-b085-401e-a65c-443e40009749", + // id can be used to specify a target deployment to update. + // Currently, it can only be used for updates once the content + // has been deployed, because the server assigns the GUIDs. + // Allowing the client to generate GUIDs and save them here + // would be helpful, since the metadata could be correct before + // the first deployment (an enabler for git workflows). + // Name can be used as an alternative to ID. + // On Connect, (owner, name) pairs must be unique + // unless name is empty. - // Is this Connect-specific? - "custom_url": "/reports/quarterly-sales/", + // Note that currently, you can't create a deployment + // by uploading a bundle, because the upload endpoint + // is under the content-id (/__api__/v1/content/{GUID}/upload). + // If we want that workflow, we will need to provide a global + // bundle upload endpoint (/__api__/v1/bundles/upload) that + // always creates a new content item. + "id": "de2e7bdb-b085-401e-a65c-443e40009749", + "name": "regional-quarterly-sales-report", // These metadata fields are purely for human consumption. "title": "Regional Quarterly Sales Report", @@ -53,7 +63,7 @@ "r": { "version": "4.3.1", "package_file": "renv.lock", - "package_manager": "packrat" + "package_manager": "renv" }, "quarto": { "version": "1.4" @@ -72,6 +82,7 @@ "recurrence": "FREQ=MONTHLY;INTERVAL=3" }, "connect": { + // These settings are Connect-specific and will be ignored by other servers. "access": { "type": "logged-in", "run_as": "rstudio-connect", @@ -92,6 +103,7 @@ "kubernetes": { // Will Posit Cloud Connect be containerized? If so, will it support // image_name, cpu_limit, memory_limit, and/or gpu settings? + // (note k8s GPU settings are different from the docker ones) "amd_gpu_limit": 0, "cpu_limit": 1, "cpu_request": 0.5, @@ -99,7 +111,7 @@ "memory_limit": "100000000", "memory_request": "20000000", "nvidia_gpu_limit": 0, - "service_account_name": "k8s-runner", + "service_account_name": "posit-connect-content", "r_environment_management": true, "py_environment_management": true } diff --git a/schema/posit-publishing-schema-v3.json b/schema/posit-publishing-schema-v3.json index 44862f0ed..db717c849 100644 --- a/schema/posit-publishing-schema-v3.json +++ b/schema/posit-publishing-schema-v3.json @@ -17,7 +17,9 @@ "type": "string", "format": "url", "description": "URL of the json-schema definition for this file. Must be 'https://cdn.posit.co/connect/posit-publishing-schema-v3.json'.", - "enum": ["./posit-publishing-schema-v3.json"], + "enum": [ + "./posit-publishing-schema-v3.json" + ], "examples": [ "./posit-publishing-schema-v3.json" ] @@ -55,15 +57,6 @@ "report.qmd" ] }, - "name": { - "type": "string", - "pattern": "^[a-zA-Z0-9_-]{3,64}$|", - "default": "", - "description": "Unique name for this deployment. If present, and a deployment with this name exists, it will be updated, otherwise it will be created.", - "examples": [ - "quarterly-sales-report-by-region" - ] - }, "id": { "type": "string", "default": null, @@ -72,13 +65,12 @@ "de2e7bdb-b085-401e-a65c-443e40009749" ] }, - "custom_url": { + "name": { "type": "string", - "pattern": "^/[/a-zA-Z0-9-_]+/$", - "default": null, - "description": "Custom URL path to assign to this content. The URL path must be unassigned, or assigned to content you own. You must be an administrator to set this value.", + "default": "", + "description": "Unique name of this deployment. If a deployment with this name does not exist, it will be created. Otherwise, it will be updated.", "examples": [ - "/reports/quarterly-sales" + "regional-quarterly-sales-report" ] }, "title": { @@ -141,10 +133,9 @@ "tags": { "type": "array", "default": [], - "description": "The tags Schema", + "description": "List of tags to apply to this deployment. When publishing to Connect, tags must be pre-defined by an administrator.", "items": { "type": "string", - "description": "List of tags to apply to this deployment. When publishing to Connect, tags must be pre-defined by an administrator.", "examples": [ "sales", "quarterly", @@ -236,14 +227,14 @@ }, "package_manager": { "type": "string", - "default": "packrat", + "default": "renv", "enum": [ - "packrat", + "renv", "none" ], "description": "Package manager that will install the dependencies. If package_manager is none, dependencies will not be installed.", "examples": [ - "packrat" + "renv" ] } } @@ -263,6 +254,22 @@ "examples": [ "1.4" ] + }, + "engines": { + "type": "array", + "default": [], + "description": "List of Quarto engines required for this content.", + "items": { + "type": "string", + "enum": [ + "knitr", + "jupyter" + ], + "examples": [ + "knitr", + "jupyter" + ] + } } } } @@ -270,7 +277,12 @@ }, "environment": { "type": "object", - "additionalProperties": true, + "additionalProperties": { + "type": [ + "string", + "null" + ] + }, "default": null, "description": "Environment variable/value map. Null values may be used for secrets; deployment tools should populate these from the deploying environment. For git-based deployments, the publishing server must be configured to provide those values.", "examples": [ From be84d0cd54e1398f7c8a0e7bce4b3adcf046adca Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Tue, 31 Oct 2023 11:17:45 -0400 Subject: [PATCH 03/18] add ACLs --- schema/example.json | 50 ++++++++++++-- schema/posit-publishing-schema-v3.json | 96 ++++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 5 deletions(-) diff --git a/schema/example.json b/schema/example.json index b6326fbc4..08a6493f9 100644 --- a/schema/example.json +++ b/schema/example.json @@ -1,8 +1,6 @@ { // URL to the schema definition, which we should make public. "$schema": "./posit-publishing-schema-v3.json", - "type": "quarto-static", - "entrypoint": "report.qmd", // id can be used to specify a target deployment to update. // Currently, it can only be used for updates once the content @@ -19,10 +17,18 @@ // is under the content-id (/__api__/v1/content/{GUID}/upload). // If we want that workflow, we will need to provide a global // bundle upload endpoint (/__api__/v1/bundles/upload) that - // always creates a new content item. + // creates or updates a content item by ID. Or, the client + // can find/create by ID/name, then upload to the content item. + // Git deployments in Connect are handled via a call sequence + // that requires a content item to be created before the repo + // can be attached to it. "id": "de2e7bdb-b085-401e-a65c-443e40009749", "name": "regional-quarterly-sales-report", + // Defines what we are deploying + "type": "quarto-static", + "entrypoint": "report.qmd", + // These metadata fields are purely for human consumption. "title": "Regional Quarterly Sales Report", "description": "This is the quarterly sales report, broken down by region.", @@ -52,7 +58,7 @@ "_quarto.yml", "model.py", "requirements.txt", - "weights/*.hdf5" + "weights/sales-model.hdf5" ], "dependencies": { "python": { @@ -84,9 +90,43 @@ "connect": { // These settings are Connect-specific and will be ignored by other servers. "access": { + // Eventually the ACL for content with type="acl" should go here. + // Identifying users is tricky, especially if the identities need + // to be ported between servers (staging/production). Connect + // assigns a GUID to each user or group, and we'd use those + // for same-server deployments because they are guaranteed to be + // unique. All other user identifiers are auth-type specific and + // need to be resolved by Connect, and many things you + // might expect to be unique are not guaranteed to be. "type": "logged-in", "run_as": "rstudio-connect", - "run_as_current_user": false + "run_as_current_user": false, + "users": [ + { + // Password auth uses usernames as unique IDs. + // You can specify the auth provider unique ID, + // Connect-assigned GUID, or both. Tools + // should populate both, because GUIDs are not + // portable across Connect instances. + "id": "jqpublic", + "guid": "536b456e-0311-4f92-ba10-dbf1db8a468e", + "name": "John Q. Public", + "permissions": "editor" + } + ], + "groups": [ + { + // Connect identifies groups with a name and a GUID, + // both of which are unique. LDAP may provide + // a CN/DN as the unique ID, with a separate name. Tools + // should populate both, because GUIDs are not + // portable across Connect instances. + "id": "Data Science Team", + "guid": "8b4fde3e-f995-4894-bc02-ae47538262ff", + "name": "Data Science Team", + "permissions": "editor" + } + ] }, "runtime": { // Might some of these apply beyond Connect? For example, init_timeout diff --git a/schema/posit-publishing-schema-v3.json b/schema/posit-publishing-schema-v3.json index db717c849..63d7acd8e 100644 --- a/schema/posit-publishing-schema-v3.json +++ b/schema/posit-publishing-schema-v3.json @@ -358,6 +358,102 @@ "examples": [ false ] + }, + "users": { + "type": "array", + "default": [], + "description": "List of users who have access to this content.", + "items": { + "type": "object", + "additionalProperties": false, + "anyOf": [ + { "required": ["id"] }, + { "required": ["guid"] } + ], + "properties": { + "id": { + "type": "string", + "default": null, + "description": "Unique ID from the authentication provider that identifies this user. This may be a username, LDAP CN/DN, email address, etc. depending on the provider.", + "examples": [ + "jqpublic" + ] + }, + "guid": { + "type": "string", + "default": null, + "description": "Unique identifier assigned by Connect. When deploying to the same server, this will determine the user who is granted access. When deploying to a different server, the provider_id will be used to look up the user.", + "examples": [ + "cd2e7cef-a195-512e-b76d-554f4f5a239c" + ] + }, + "name": { + "type": "string", + "default": null, + "description": "User's name. This field is informational only.", + "examples": [ + "John Q. Public" + ] + }, + "permissions": { + "type": "string", + "default": "viewer", + "description": "Permission level assigned to this user.", + "enum": ["viewer", "editor", "owner"], + "examples": [ + "viewer" + ] + } + } + } + }, + "groups": { + "type": "array", + "default": [], + "description": "List of groups who have access to this content.", + "items": { + "type": "object", + "additionalProperties": false, + "anyOf": [ + { "required": ["id"] }, + { "required": ["guid"] } + ], + "properties": { + "id": { + "type": "string", + "default": null, + "description": "Unique ID from the authentication provider that identifies this group. For groups created by Connect, this will be the group name. This may be a group name, LDAP CN/DN, etc. depending on the provider.", + "examples": [ + "Data Science Team" + ] + }, + "guid": { + "type": "string", + "default": null, + "description": "Unique identifier assigned by Connect. When deploying to the same server, this will determine the user who is granted access. When deploying to a different server, the provider_id will be used to look up the user.", + "examples": [ + "8b4fde3e-f995-4894-bc02-ae47538262ff" + ] + }, + "name": { + "type": "string", + "default": null, + "description": "Group name. This field is informational only.", + "examples": [ + "Data Science Team" + ] + }, + "permissions": { + "type": "string", + "default": "viewer", + "description": "Permission level assigned to this group.", + "enum": ["viewer", "editor", "owner"], + "examples": [ + "editor" + ] + } + } + } } } }, From 984b431f2036c296589de3ec6517d96353e2d47f Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Thu, 2 Nov 2023 11:19:19 -0400 Subject: [PATCH 04/18] split records from configuration --- schema/example.json | 24 +-------- schema/posit-publishing-record-schema-v3.json | 49 +++++++++++++++++++ schema/posit-publishing-schema-v3.json | 30 +++++------- schema/record.json | 6 +++ 4 files changed, 67 insertions(+), 42 deletions(-) create mode 100644 schema/posit-publishing-record-schema-v3.json create mode 100644 schema/record.json diff --git a/schema/example.json b/schema/example.json index 08a6493f9..36d145344 100644 --- a/schema/example.json +++ b/schema/example.json @@ -2,29 +2,6 @@ // URL to the schema definition, which we should make public. "$schema": "./posit-publishing-schema-v3.json", - // id can be used to specify a target deployment to update. - // Currently, it can only be used for updates once the content - // has been deployed, because the server assigns the GUIDs. - // Allowing the client to generate GUIDs and save them here - // would be helpful, since the metadata could be correct before - // the first deployment (an enabler for git workflows). - // Name can be used as an alternative to ID. - // On Connect, (owner, name) pairs must be unique - // unless name is empty. - - // Note that currently, you can't create a deployment - // by uploading a bundle, because the upload endpoint - // is under the content-id (/__api__/v1/content/{GUID}/upload). - // If we want that workflow, we will need to provide a global - // bundle upload endpoint (/__api__/v1/bundles/upload) that - // creates or updates a content item by ID. Or, the client - // can find/create by ID/name, then upload to the content item. - // Git deployments in Connect are handled via a call sequence - // that requires a content item to be created before the repo - // can be attached to it. - "id": "de2e7bdb-b085-401e-a65c-443e40009749", - "name": "regional-quarterly-sales-report", - // Defines what we are deploying "type": "quarto-static", "entrypoint": "report.qmd", @@ -32,6 +9,7 @@ // These metadata fields are purely for human consumption. "title": "Regional Quarterly Sales Report", "description": "This is the quarterly sales report, broken down by region.", + "thumbnail": "images/thumbnail.jpg", "author": { "name": "Michael Marchetti", "email": "mike@posit.co", diff --git a/schema/posit-publishing-record-schema-v3.json b/schema/posit-publishing-record-schema-v3.json new file mode 100644 index 000000000..80e41fc22 --- /dev/null +++ b/schema/posit-publishing-record-schema-v3.json @@ -0,0 +1,49 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/rstudio/publishing-client/posit-publishing-record-schema-v3.json", + "type": "object", + "additionalProperties": false, + "default": {}, + "description": "Posit Publishing Record", + "required": [ + "$schema", + "server-url", + "id", + "configuration" + ], + "properties": { + "$schema": { + "type": "string", + "format": "url", + "description": "URL of the json-schema definition for this file. Must be 'https://cdn.posit.co/connect/posit-publishing-schema-v3.json'.", + "enum": [ + "./posit-publishing-record-schema-v3.json" + ], + "examples": [ + "./posit-publishing-record-schema-v3.json" + ] + }, + "server-url": { + "type": "string", + "format": "uri", + "description": "URL of the server where this content was deployed.", + "examples": [ + "https://connect.example.com" + ] + }, + "id": { + "type": "string", + "description": "Unique ID of this deployment.", + "examples": [ + "de2e7bdb-b085-401e-a65c-443e40009749" + ] + }, + "configuration": { + "type": "string", + "description": "Path to the configuration file within the project directory.", + "examples": [ + "production.json" + ] + } + } +} diff --git a/schema/posit-publishing-schema-v3.json b/schema/posit-publishing-schema-v3.json index 63d7acd8e..ed8fbac6f 100644 --- a/schema/posit-publishing-schema-v3.json +++ b/schema/posit-publishing-schema-v3.json @@ -4,12 +4,11 @@ "type": "object", "additionalProperties": false, "default": {}, - "description": "Posit Deployment", + "description": "Posit Deployment Settings", "required": [ "$schema", "type", "entrypoint", - "files", "dependencies" ], "properties": { @@ -57,22 +56,6 @@ "report.qmd" ] }, - "id": { - "type": "string", - "default": null, - "description": "Unique ID of this deployment. If present, it must be the ID of an existing deployment of the same type on the server, which will be updated.", - "examples": [ - "de2e7bdb-b085-401e-a65c-443e40009749" - ] - }, - "name": { - "type": "string", - "default": "", - "description": "Unique name of this deployment. If a deployment with this name does not exist, it will be created. Otherwise, it will be updated.", - "examples": [ - "regional-quarterly-sales-report" - ] - }, "title": { "type": "string", "pattern": "^[^\t\n\f\r]{3,1024}$|", @@ -90,6 +73,14 @@ "This is the quarterly sales report, broken down by region." ] }, + "thumbnail": { + "type": "string", + "default": null, + "description": "Path to thumbnail preview image for this content.", + "examples": [ + "images/thumbnail.jpg" + ] + }, "author": { "type": "object", "additionalProperties": false, @@ -145,7 +136,8 @@ }, "files": { "type": "array", - "description": "The files Schema", + "description": "List of files to include in the deployment. The default is to include all files not matched by a pattern in an ignore file.", + "default": null, "items": { "type": "string", "description": "List of relative file paths that are used by this deployment. When creating a bundle, these files must exist in the project directory. When deploying from git, these files must exist in the repository.", diff --git a/schema/record.json b/schema/record.json new file mode 100644 index 000000000..8f6b1f7ce --- /dev/null +++ b/schema/record.json @@ -0,0 +1,6 @@ +{ + "$schema": "./posit-publishing-record-schema-v3.json", + "server-url": "https://connect.example.com", + "id": "de2e7bdb-b085-401e-a65c-443e40009749", + "configuration": "production.json" +} From becf9b371e4160caca9aa56568f2d6b6bf4fc836 Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Thu, 2 Nov 2023 11:19:49 -0400 Subject: [PATCH 05/18] renaming --- schema/{example.json => deploy.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename schema/{example.json => deploy.json} (100%) diff --git a/schema/example.json b/schema/deploy.json similarity index 100% rename from schema/example.json rename to schema/deploy.json From 987de2ddaa17f8ca461a07f2b3b05924379c244c Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Fri, 3 Nov 2023 09:59:40 -0400 Subject: [PATCH 06/18] add toml and yaml examples --- schema/deploy.toml | 78 ++++++++++++++++++++++++++++++++++++++++++++++ schema/deploy.yml | 74 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 schema/deploy.toml create mode 100644 schema/deploy.yml diff --git a/schema/deploy.toml b/schema/deploy.toml new file mode 100644 index 000000000..578ca1c92 --- /dev/null +++ b/schema/deploy.toml @@ -0,0 +1,78 @@ +"$schema" = "./posit-publishing-schema-v3.json" +type = "quarto-static" +entrypoint = "report.qmd" +title = "Regional Quarterly Sales Report" +description = "This is the quarterly sales report, broken down by region." +thumbnail = "images/thumbnail.jpg" +tags = [ "sales", "quarterly", "regional" ] +files = [ + "report.qmd", + "_quarto.yml", + "model.py", + "requirements.txt", + "weights/sales-model.hdf5" +] + +[author] +name = "Michael Marchetti" +email = "mike@posit.co" +username = "mmarchetti" +organization = "Posit, PBC" + +[dependencies.python] +version = "3.11.3" +package_file = "requirements.txt" +package_manager = "pip" + +[dependencies.r] +version = "4.3.1" +package_file = "renv.lock" +package_manager = "renv" + +[dependencies.quarto] +version = "1.4" + +[environment] +API_URL = "https://example.com/api" + +[schedule] +start = "2023-10-25T08:00:00Z" +recurrence = "FREQ=MONTHLY;INTERVAL=3" + +[[access.users]] +id = "jqpublic" +guid = "536b456e-0311-4f92-ba10-dbf1db8a468e" +name = "John Q. Public" +permissions = "editor" + +[[access.groups]] +id = "Data Science Team" +guid = "8b4fde3e-f995-4894-bc02-ae47538262ff" +name = "Data Science Team" +permissions = "editor" + +[connect.access] +run_as = "rstudio-connect" +run_as_current_user = false + +[connect.runtime] +connection_timeout = 5 +read_timeout = 30 +init_timeout = 60 +idle_timeout = 120 +max_processes = 5 +min_processes = 1 +max_conns_per_process = 50 +load_factor = 0.5 + +[connect.kubernetes] +amd_gpu_limit = 0 +cpu_limit = 1 +cpu_request = 0.5 +image_name = "posit/connect-runtime-python3.11-r4.3" +memory_limit = "100000000" +memory_request = "20000000" +nvidia_gpu_limit = 0 +service_account_name = "posit-connect-content" +r_environment_management = true +py_environment_management = true diff --git a/schema/deploy.yml b/schema/deploy.yml new file mode 100644 index 000000000..40842e906 --- /dev/null +++ b/schema/deploy.yml @@ -0,0 +1,74 @@ +# yaml-language-server: $schema=./posit-publishing-schema-v3.json +"$schema": "./posit-publishing-schema-v3.json" +type: quarto-static +entrypoint: report.qmd +title: Regional Quarterly Sales Report +description: This is the quarterly sales report, broken down by region. +thumbnail: images/thumbnail.jpg +author: + name: Michael Marchetti + email: mike@posit.co + username: mmarchetti + organization: Posit, PBC +tags: +- sales +- quarterly +- regional +files: +- report.qmd +- _quarto.yml +- model.py +- requirements.txt +- weights/sales-model.hdf5 +dependencies: + python: + version: 3.11.3 + package_file: requirements.txt + package_manager: pip + r: + version: 4.3.1 + package_file: renv.lock + package_manager: renv + quarto: + version: '1.4' +environment: + API_URL: https://example.com/api + API_KEY: +schedule: + start: '2023-10-25T08:00:00Z' + recurrence: FREQ=MONTHLY;INTERVAL=3 +access: + users: + - id: jqpublic + guid: 536b456e-0311-4f92-ba10-dbf1db8a468e + name: John Q. Public + permissions: editor + groups: + - id: Data Science Team + guid: 8b4fde3e-f995-4894-bc02-ae47538262ff + name: Data Science Team + permissions: editor +connect: + access: + run_as: rstudio-connect + run_as_current_user: false + runtime: + connection_timeout: 5 + read_timeout: 30 + init_timeout: 60 + idle_timeout: 120 + max_processes: 5 + min_processes: 1 + max_conns_per_process: 50 + load_factor: 0.5 + kubernetes: + amd_gpu_limit: 0 + cpu_limit: 1 + cpu_request: 0.5 + image_name: posit/connect-runtime-python3.11-r4.3 + memory_limit: '100000000' + memory_request: '20000000' + nvidia_gpu_limit: 0 + service_account_name: posit-connect-content + r_environment_management: true + py_environment_management: true From acc750424491b58ab55d818dd4f0c4deabcf8388 Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Fri, 3 Nov 2023 11:48:57 -0400 Subject: [PATCH 07/18] move access to top level --- schema/deploy.json | 65 +++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 36 deletions(-) diff --git a/schema/deploy.json b/schema/deploy.json index 36d145344..e2277ef5e 100644 --- a/schema/deploy.json +++ b/schema/deploy.json @@ -65,46 +65,39 @@ "start": "2023-10-25T08:00:00Z", "recurrence": "FREQ=MONTHLY;INTERVAL=3" }, + "access": { + "users": [ + { + // Password auth uses usernames as unique IDs. + // You can specify the auth provider unique ID, + // Connect-assigned GUID, or both. Tools + // should populate both, because GUIDs are not + // portable across Connect instances. + "id": "jqpublic", + "guid": "536b456e-0311-4f92-ba10-dbf1db8a468e", + "name": "John Q. Public", + "permissions": "editor" + } + ], + "groups": [ + { + // Connect identifies groups with a name and a GUID, + // both of which are unique. LDAP may provide + // a CN/DN as the unique ID, with a separate name. Tools + // should populate both, because GUIDs are not + // portable across Connect instances. + "id": "Data Science Team", + "guid": "8b4fde3e-f995-4894-bc02-ae47538262ff", + "name": "Data Science Team", + "permissions": "editor" + } + ] + }, "connect": { // These settings are Connect-specific and will be ignored by other servers. "access": { - // Eventually the ACL for content with type="acl" should go here. - // Identifying users is tricky, especially if the identities need - // to be ported between servers (staging/production). Connect - // assigns a GUID to each user or group, and we'd use those - // for same-server deployments because they are guaranteed to be - // unique. All other user identifiers are auth-type specific and - // need to be resolved by Connect, and many things you - // might expect to be unique are not guaranteed to be. - "type": "logged-in", "run_as": "rstudio-connect", - "run_as_current_user": false, - "users": [ - { - // Password auth uses usernames as unique IDs. - // You can specify the auth provider unique ID, - // Connect-assigned GUID, or both. Tools - // should populate both, because GUIDs are not - // portable across Connect instances. - "id": "jqpublic", - "guid": "536b456e-0311-4f92-ba10-dbf1db8a468e", - "name": "John Q. Public", - "permissions": "editor" - } - ], - "groups": [ - { - // Connect identifies groups with a name and a GUID, - // both of which are unique. LDAP may provide - // a CN/DN as the unique ID, with a separate name. Tools - // should populate both, because GUIDs are not - // portable across Connect instances. - "id": "Data Science Team", - "guid": "8b4fde3e-f995-4894-bc02-ae47538262ff", - "name": "Data Science Team", - "permissions": "editor" - } - ] + "run_as_current_user": false }, "runtime": { // Might some of these apply beyond Connect? For example, init_timeout From 16bd0afdfe3306460004323aba61c9ba870a34f3 Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Fri, 3 Nov 2023 11:49:11 -0400 Subject: [PATCH 08/18] selective use of defaults --- schema/posit-publishing-record-schema-v3.json | 1 - schema/posit-publishing-schema-v3.json | 287 ++++++++---------- 2 files changed, 119 insertions(+), 169 deletions(-) diff --git a/schema/posit-publishing-record-schema-v3.json b/schema/posit-publishing-record-schema-v3.json index 80e41fc22..86f2b8ae2 100644 --- a/schema/posit-publishing-record-schema-v3.json +++ b/schema/posit-publishing-record-schema-v3.json @@ -3,7 +3,6 @@ "$id": "https://github.com/rstudio/publishing-client/posit-publishing-record-schema-v3.json", "type": "object", "additionalProperties": false, - "default": {}, "description": "Posit Publishing Record", "required": [ "$schema", diff --git a/schema/posit-publishing-schema-v3.json b/schema/posit-publishing-schema-v3.json index ed8fbac6f..31a35358d 100644 --- a/schema/posit-publishing-schema-v3.json +++ b/schema/posit-publishing-schema-v3.json @@ -3,7 +3,6 @@ "$id": "https://github.com/rstudio/publishing-client/posit-publishing-schema-v3.json", "type": "object", "additionalProperties": false, - "default": {}, "description": "Posit Deployment Settings", "required": [ "$schema", @@ -59,7 +58,6 @@ "title": { "type": "string", "pattern": "^[^\t\n\f\r]{3,1024}$|", - "default": "", "description": "Human-readable title for this content.", "examples": [ "Quarterly Sales Report" @@ -67,7 +65,6 @@ }, "description": { "type": "string", - "default": "", "description": "Human-readable description for this content.", "examples": [ "This is the quarterly sales report, broken down by region." @@ -75,7 +72,6 @@ }, "thumbnail": { "type": "string", - "default": null, "description": "Path to thumbnail preview image for this content.", "examples": [ "images/thumbnail.jpg" @@ -84,12 +80,10 @@ "author": { "type": "object", "additionalProperties": false, - "default": null, "description": "Information about the author of this content.", "properties": { "name": { "type": "string", - "default": "", "description": "Author name.", "examples": [ "Michael Marchetti" @@ -97,7 +91,7 @@ }, "email": { "type": "string", - "default": "", + "format": "email", "description": "Author email address.", "examples": [ "mike@posit.co" @@ -105,7 +99,6 @@ }, "username": { "type": "string", - "default": "", "description": "Author's username on the publishing server.", "examples": [ "mmarchetti" @@ -113,7 +106,6 @@ }, "organization": { "type": "string", - "default": "", "description": "Author's organization.", "examples": [ "Posit, PBC" @@ -123,7 +115,6 @@ }, "tags": { "type": "array", - "default": [], "description": "List of tags to apply to this deployment. When publishing to Connect, tags must be pre-defined by an administrator.", "items": { "type": "string", @@ -137,7 +128,6 @@ "files": { "type": "array", "description": "List of files to include in the deployment. The default is to include all files not matched by a pattern in an ignore file.", - "default": null, "items": { "type": "string", "description": "List of relative file paths that are used by this deployment. When creating a bundle, these files must exist in the project directory. When deploying from git, these files must exist in the repository.", @@ -158,7 +148,6 @@ "python": { "type": "object", "additionalProperties": false, - "default": null, "description": "Python language and dependencies.", "required": [ "version" @@ -196,7 +185,6 @@ "r": { "type": "object", "additionalProperties": false, - "default": null, "description": "R language and dependencies.", "required": [ "version" @@ -234,7 +222,6 @@ "quarto": { "type": "object", "additionalProperties": false, - "default": null, "description": "Quarto version required to run the content.", "required": [ "version" @@ -249,7 +236,6 @@ }, "engines": { "type": "array", - "default": [], "description": "List of Quarto engines required for this content.", "items": { "type": "string", @@ -275,7 +261,6 @@ "null" ] }, - "default": null, "description": "Environment variable/value map. Null values may be used for secrets; deployment tools should populate these from the deploying environment. For git-based deployments, the publishing server must be configured to provide those values.", "examples": [ { @@ -287,7 +272,6 @@ "schedule": { "type": "object", "additionalProperties": false, - "default": null, "description": "Schedule for recurring execution of this content. Only applies to reports, such as Quarto and R Markdown.", "required": [ "start", @@ -310,154 +294,139 @@ } } }, - "connect": { + "access": { "type": "object", "additionalProperties": false, - "default": null, - "description": "Setting specific to Posit Connect deployments.", + "description": "Access control settings.", "properties": { - "access": { - "type": "object", - "additionalProperties": false, - "default": null, - "description": "Access control settings.", - "properties": { - "type": { - "type": "string", - "default": "acl", - "description": "Type of access control. 'all' make the deployment public, with no login required. 'logged-in' allows all logged-in users to access the content. 'acl' allows only specific users to access the content.", - "enum": [ - "all", - "logged-in", - "acl" - ], - "examples": [ - "logged-in" - ] - }, - "run_as": { - "type": "string", - "default": "", - "description": "The system username under which the content should be run. Must be an existing user in the allowed group. You must be an administrator to set this value.", - "examples": [ - "rstudio-connect" - ] - }, - "run_as_current_user": { - "type": "boolean", - "default": false, - "description": "For application content types, run a separate process for each visiting user under that user's server account. Requires PAM authentication on the Posit Connect server. You must be an administrator to set this value.", - "examples": [ - false - ] - }, - "users": { - "type": "array", - "default": [], - "description": "List of users who have access to this content.", - "items": { - "type": "object", - "additionalProperties": false, - "anyOf": [ - { "required": ["id"] }, - { "required": ["guid"] } - ], - "properties": { - "id": { - "type": "string", - "default": null, - "description": "Unique ID from the authentication provider that identifies this user. This may be a username, LDAP CN/DN, email address, etc. depending on the provider.", - "examples": [ - "jqpublic" - ] - }, - "guid": { - "type": "string", - "default": null, - "description": "Unique identifier assigned by Connect. When deploying to the same server, this will determine the user who is granted access. When deploying to a different server, the provider_id will be used to look up the user.", - "examples": [ - "cd2e7cef-a195-512e-b76d-554f4f5a239c" - ] - }, - "name": { - "type": "string", - "default": null, - "description": "User's name. This field is informational only.", - "examples": [ - "John Q. Public" - ] - }, - "permissions": { - "type": "string", - "default": "viewer", - "description": "Permission level assigned to this user.", - "enum": ["viewer", "editor", "owner"], - "examples": [ - "viewer" - ] - } - } + "type": { + "type": "string", + "default": "acl", + "description": "Type of access control. 'public' make the deployment public (no login required), with no login required. 'all-users' allows all logged-in users to access the content. 'acl' (the default) allows only specific users and groups to access the content.", + "enum": [ + "public", + "all-users", + "acl" + ], + "examples": [ + "all-users" + ] + }, + "users": { + "type": "array", + "description": "List of users who have access to this content.", + "items": { + "type": "object", + "additionalProperties": false, + "anyOf": [ + { "required": ["id"] }, + { "required": ["guid"] } + ], + "properties": { + "id": { + "type": "string", + "description": "Unique ID from the authentication provider that identifies this user. This may be a username, LDAP CN/DN, email address, etc. depending on the provider.", + "examples": [ + "jqpublic" + ] + }, + "guid": { + "type": "string", + "description": "Unique identifier assigned by Connect. When deploying to the same server, this will determine the user who is granted access. When deploying to a different server, the provider_id will be used to look up the user.", + "examples": [ + "cd2e7cef-a195-512e-b76d-554f4f5a239c" + ] + }, + "name": { + "type": "string", + "description": "User's name. This field is informational only.", + "examples": [ + "John Q. Public" + ] + }, + "permissions": { + "type": "string", + "description": "Permission level assigned to this user.", + "enum": ["viewer", "editor", "owner"], + "examples": [ + "viewer" + ] } - }, - "groups": { - "type": "array", - "default": [], - "description": "List of groups who have access to this content.", - "items": { - "type": "object", - "additionalProperties": false, - "anyOf": [ - { "required": ["id"] }, - { "required": ["guid"] } - ], - "properties": { - "id": { - "type": "string", - "default": null, - "description": "Unique ID from the authentication provider that identifies this group. For groups created by Connect, this will be the group name. This may be a group name, LDAP CN/DN, etc. depending on the provider.", - "examples": [ - "Data Science Team" - ] - }, - "guid": { - "type": "string", - "default": null, - "description": "Unique identifier assigned by Connect. When deploying to the same server, this will determine the user who is granted access. When deploying to a different server, the provider_id will be used to look up the user.", - "examples": [ - "8b4fde3e-f995-4894-bc02-ae47538262ff" - ] - }, - "name": { - "type": "string", - "default": null, - "description": "Group name. This field is informational only.", - "examples": [ - "Data Science Team" - ] - }, - "permissions": { - "type": "string", - "default": "viewer", - "description": "Permission level assigned to this group.", - "enum": ["viewer", "editor", "owner"], - "examples": [ - "editor" - ] - } - } + } + } + }, + "groups": { + "type": "array", + "description": "List of groups who have access to this content.", + "items": { + "type": "object", + "additionalProperties": false, + "anyOf": [ + { "required": ["id"] }, + { "required": ["guid"] } + ], + "properties": { + "id": { + "type": "string", + "description": "Unique ID from the authentication provider that identifies this group. For groups created by Connect, this will be the group name. This may be a group name, LDAP CN/DN, etc. depending on the provider.", + "examples": [ + "Data Science Team" + ] + }, + "guid": { + "type": "string", + "description": "Unique identifier assigned by Connect. When deploying to the same server, this will determine the user who is granted access. When deploying to a different server, the provider_id will be used to look up the user.", + "examples": [ + "8b4fde3e-f995-4894-bc02-ae47538262ff" + ] + }, + "name": { + "type": "string", + "description": "Group name. This field is informational only.", + "examples": [ + "Data Science Team" + ] + }, + "permissions": { + "type": "string", + "default": "viewer", + "description": "Permission level assigned to this group.", + "enum": ["viewer", "editor", "owner"], + "examples": [ + "editor" + ] } } } + } + } + }, + "connect": { + "type": "object", + "additionalProperties": false, + "description": "Setting specific to Posit Connect deployments.", + "properties": { + "access": { + "run_as": { + "type": "string", + "description": "The system username under which the content should be run. Must be an existing user in the allowed group. You must be an administrator to set this value.", + "examples": [ + "rstudio-connect" + ] + }, + "run_as_current_user": { + "type": "boolean", + "default": false, + "description": "For application content types, run a separate process for each visiting user under that user's server account. Requires PAM authentication on the Posit Connect server. You must be an administrator to set this value." + } }, "runtime": { "type": "object", "additionalProperties": false, - "default": null, "description": "Runtime settings for application content types.", "properties": { "connection_timeout": { "type": "integer", - "default": null, "minimum": 0, "description": "Maximum number of seconds allowed without data sent or received across a client connection. A value of `0` means connections will never time-out (not recommended).", "examples": [ @@ -466,7 +435,6 @@ }, "read_timeout": { "type": "integer", - "default": null, "minimum": 0, "description": "Maximum number of seconds allowed without data received from a client connection. A value of `0` means a lack of client (browser) interaction never causes the connection to close.", "examples": [ @@ -475,7 +443,6 @@ }, "init_timeout": { "type": "integer", - "default": null, "description": "The maximum number of seconds allowed for an interactive application to start. Posit Connect must be able to connect to a newly launched application before this threshold has elapsed.", "examples": [ 60 @@ -483,7 +450,6 @@ }, "idle_timeout": { "type": "integer", - "default": null, "description": "The maximum number of seconds a worker process for an interactive application to remain alive after it goes idle (no active connections).", "examples": [ 120 @@ -491,7 +457,6 @@ }, "max_processes": { "type": "integer", - "default": null, "minimum": 1, "description": "Specifies the total number of concurrent processes allowed for a single interactive application.", "examples": [ @@ -500,7 +465,6 @@ }, "min_processes": { "type": "integer", - "default": null, "minimum": 0, "description": "Specifies the minimum number of concurrent processes allowed for a single interactive application.", "examples": [ @@ -509,7 +473,6 @@ }, "max_conns_per_process": { "type": "integer", - "default": null, "minimum": 1, "description": "Specifies the maximum number of client connections allowed to an individual process. Incoming connections which will exceed this limit are routed to a new process or rejected.", "examples": [ @@ -518,7 +481,6 @@ }, "load_factor": { "type": "number", - "default": null, "minimum": 0, "maximum": 1, "description": "Controls how aggressively new processes are spawned. The valid range is between 0.0 and 1.0.", @@ -531,12 +493,10 @@ "kubernetes": { "type": "object", "additionalProperties": false, - "default": null, "description": "Settings used with Posit Connect's off-host execution feature, where content is run in Kubernetes.", "properties": { "amd_gpu_limit": { "type": "integer", - "default": null, "description": "The number of AMD GPUs that will be allocated by Kubernetes to run this content.", "examples": [ 0 @@ -544,7 +504,6 @@ }, "cpu_limit": { "type": "integer", - "default": null, "description": "The maximum amount of compute power this content will be allowed to consume when executing or rendering, expressed in CPU Units, where 1.0 unit is equivalent to 1 physical or virtual core. Fractional values are allowed. If the process tries to use more CPU than allowed, it will be throttled.", "examples": [ 1 @@ -552,7 +511,6 @@ }, "cpu_request": { "type": "number", - "default": null, "description": "The minimum amount of compute power this content needs when executing virtual core. Fractional values are allowed.", "examples": [ 0.5 @@ -560,7 +518,6 @@ }, "image_name": { "type": "string", - "default": "", "description": "Name of the target container image.", "examples": [ "posit/connect-runtime-python3.11-r4.3" @@ -568,7 +525,6 @@ }, "memory_limit": { "type": "string", - "default": "", "description": "The maximum amount of RAM this content will be allowed to consume when executing or rendering, expressed in bytes. If the process tries to use more memory than allowed, it will be terminated", "examples": [ "100000000" @@ -576,7 +532,6 @@ }, "memory_request": { "type": "string", - "default": "", "description": "The minimum amount of RAM this content needs when executing or rendering, expressed in bytes.", "examples": [ "20000000" @@ -584,7 +539,6 @@ }, "nvidia_gpu_limit": { "type": "integer", - "default": null, "description": "The number of NVIDIA GPUs that will be allocated by Kubernetes to run this content.", "examples": [ 0 @@ -592,7 +546,6 @@ }, "service_account_name": { "type": "string", - "default": null, "description": "The name of the Kubernetes service account that is used to run this content. It must adhere to Kubernetes service account naming rules. You must be an administrator to set this value.", "examples": [ "posit-connect-content" @@ -600,7 +553,6 @@ }, "r_environment_management": { "type": "boolean", - "default": false, "description": "Enables or disables R environment management. When false, Posit Connect will not install R packages and instead assume that all required packages are present in the container image.", "examples": [ true @@ -608,7 +560,6 @@ }, "py_environment_management": { "type": "boolean", - "default": false, "description": "Enables or disables Python environment management. When false, Posit Connect will not install Python packages and instead assume that all required packages are present in the container image.", "examples": [ true From af8072e8c28e1394f76256956fc0fbe8672112df Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Wed, 8 Nov 2023 14:26:08 -0500 Subject: [PATCH 09/18] add other python package managers --- schema/posit-publishing-schema-v3.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/schema/posit-publishing-schema-v3.json b/schema/posit-publishing-schema-v3.json index 31a35358d..1939d4544 100644 --- a/schema/posit-publishing-schema-v3.json +++ b/schema/posit-publishing-schema-v3.json @@ -173,6 +173,9 @@ "default": "pip", "enum": [ "pip", + "conda", + "pipenv", + "poetry", "none" ], "description": "Package manager that will install the dependencies. If package_manager is none, dependencies will not be installed.", From 643c125ee58c607f3a05abc93abd5cdb6ef88837 Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Thu, 9 Nov 2023 11:03:01 -0500 Subject: [PATCH 10/18] standarize on TOML --- schema/deploy.json | 130 --------------------------------------------- schema/deploy.yml | 74 -------------------------- 2 files changed, 204 deletions(-) delete mode 100644 schema/deploy.json delete mode 100644 schema/deploy.yml diff --git a/schema/deploy.json b/schema/deploy.json deleted file mode 100644 index e2277ef5e..000000000 --- a/schema/deploy.json +++ /dev/null @@ -1,130 +0,0 @@ -{ - // URL to the schema definition, which we should make public. - "$schema": "./posit-publishing-schema-v3.json", - - // Defines what we are deploying - "type": "quarto-static", - "entrypoint": "report.qmd", - - // These metadata fields are purely for human consumption. - "title": "Regional Quarterly Sales Report", - "description": "This is the quarterly sales report, broken down by region.", - "thumbnail": "images/thumbnail.jpg", - "author": { - "name": "Michael Marchetti", - "email": "mike@posit.co", - "username": "mmarchetti", - "organization": "Posit, PBC" - }, - // Arbitrary string tags. On Connect, only admins can create tags, - // and tags can be nested (e.g. "sales,reports,quarterly" is a - // single tag 3 levels deep in the tag tree). The example below - // is 3 separate top level tags. - "tags": [ - "sales", - "quarterly", - "regional" - ], - // List of (relative) file paths. If constructing a bundle to upload, - // these files must be included. For git deployments, the server - // will expect (required) that these files be present in the repo. - // Unlike manifest v1, there are no checksums, since those require - // updating this file whenever other files change, and we'd prefer - // to write this once and commit it. - "files": [ - "report.qmd", - "_quarto.yml", - "model.py", - "requirements.txt", - "weights/sales-model.hdf5" - ], - "dependencies": { - "python": { - "version": "3.11.3", - "package_file": "requirements.txt", - "package_manager": "pip" - }, - "r": { - "version": "4.3.1", - "package_file": "renv.lock", - "package_manager": "renv" - }, - "quarto": { - "version": "1.4" - } - }, - "environment": { - "API_URL": "https://example.com/api", - // secret value, will be pulled from the environment when deploying through CLI, - // must be set at the server if deploying through git. - "API_KEY": null - }, - "schedule": { - // Only valid for reports, not for apps. - // iCalendar spec: https://icalendar.org/iCalendar-RFC-5545/3-8-5-3-recurrence-rule.html - "start": "2023-10-25T08:00:00Z", - "recurrence": "FREQ=MONTHLY;INTERVAL=3" - }, - "access": { - "users": [ - { - // Password auth uses usernames as unique IDs. - // You can specify the auth provider unique ID, - // Connect-assigned GUID, or both. Tools - // should populate both, because GUIDs are not - // portable across Connect instances. - "id": "jqpublic", - "guid": "536b456e-0311-4f92-ba10-dbf1db8a468e", - "name": "John Q. Public", - "permissions": "editor" - } - ], - "groups": [ - { - // Connect identifies groups with a name and a GUID, - // both of which are unique. LDAP may provide - // a CN/DN as the unique ID, with a separate name. Tools - // should populate both, because GUIDs are not - // portable across Connect instances. - "id": "Data Science Team", - "guid": "8b4fde3e-f995-4894-bc02-ae47538262ff", - "name": "Data Science Team", - "permissions": "editor" - } - ] - }, - "connect": { - // These settings are Connect-specific and will be ignored by other servers. - "access": { - "run_as": "rstudio-connect", - "run_as_current_user": false - }, - "runtime": { - // Might some of these apply beyond Connect? For example, init_timeout - // tells the server how long this app might take to start up. - "connection_timeout": 5, - "read_timeout": 30, - "init_timeout": 60, - "idle_timeout": 120, - "max_processes": 5, - "min_processes": 1, - "max_conns_per_process": 50, - "load_factor": 0.5 - }, - "kubernetes": { - // Will Posit Cloud Connect be containerized? If so, will it support - // image_name, cpu_limit, memory_limit, and/or gpu settings? - // (note k8s GPU settings are different from the docker ones) - "amd_gpu_limit": 0, - "cpu_limit": 1, - "cpu_request": 0.5, - "image_name": "posit/connect-runtime-python3.11-r4.3", - "memory_limit": "100000000", - "memory_request": "20000000", - "nvidia_gpu_limit": 0, - "service_account_name": "posit-connect-content", - "r_environment_management": true, - "py_environment_management": true - } - } -} diff --git a/schema/deploy.yml b/schema/deploy.yml deleted file mode 100644 index 40842e906..000000000 --- a/schema/deploy.yml +++ /dev/null @@ -1,74 +0,0 @@ -# yaml-language-server: $schema=./posit-publishing-schema-v3.json -"$schema": "./posit-publishing-schema-v3.json" -type: quarto-static -entrypoint: report.qmd -title: Regional Quarterly Sales Report -description: This is the quarterly sales report, broken down by region. -thumbnail: images/thumbnail.jpg -author: - name: Michael Marchetti - email: mike@posit.co - username: mmarchetti - organization: Posit, PBC -tags: -- sales -- quarterly -- regional -files: -- report.qmd -- _quarto.yml -- model.py -- requirements.txt -- weights/sales-model.hdf5 -dependencies: - python: - version: 3.11.3 - package_file: requirements.txt - package_manager: pip - r: - version: 4.3.1 - package_file: renv.lock - package_manager: renv - quarto: - version: '1.4' -environment: - API_URL: https://example.com/api - API_KEY: -schedule: - start: '2023-10-25T08:00:00Z' - recurrence: FREQ=MONTHLY;INTERVAL=3 -access: - users: - - id: jqpublic - guid: 536b456e-0311-4f92-ba10-dbf1db8a468e - name: John Q. Public - permissions: editor - groups: - - id: Data Science Team - guid: 8b4fde3e-f995-4894-bc02-ae47538262ff - name: Data Science Team - permissions: editor -connect: - access: - run_as: rstudio-connect - run_as_current_user: false - runtime: - connection_timeout: 5 - read_timeout: 30 - init_timeout: 60 - idle_timeout: 120 - max_processes: 5 - min_processes: 1 - max_conns_per_process: 50 - load_factor: 0.5 - kubernetes: - amd_gpu_limit: 0 - cpu_limit: 1 - cpu_request: 0.5 - image_name: posit/connect-runtime-python3.11-r4.3 - memory_limit: '100000000' - memory_request: '20000000' - nvidia_gpu_limit: 0 - service_account_name: posit-connect-content - r_environment_management: true - py_environment_management: true From ad9e87aa1002804ca303bff55213ef5f2baaa6ca Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Thu, 9 Nov 2023 12:52:49 -0500 Subject: [PATCH 11/18] schema updates --- schema/deploy.toml | 13 --- schema/posit-publishing-schema-v3.json | 121 ++++++++----------------- 2 files changed, 37 insertions(+), 97 deletions(-) diff --git a/schema/deploy.toml b/schema/deploy.toml index 578ca1c92..5e26685a2 100644 --- a/schema/deploy.toml +++ b/schema/deploy.toml @@ -5,19 +5,6 @@ title = "Regional Quarterly Sales Report" description = "This is the quarterly sales report, broken down by region." thumbnail = "images/thumbnail.jpg" tags = [ "sales", "quarterly", "regional" ] -files = [ - "report.qmd", - "_quarto.yml", - "model.py", - "requirements.txt", - "weights/sales-model.hdf5" -] - -[author] -name = "Michael Marchetti" -email = "mike@posit.co" -username = "mmarchetti" -organization = "Posit, PBC" [dependencies.python] version = "3.11.3" diff --git a/schema/posit-publishing-schema-v3.json b/schema/posit-publishing-schema-v3.json index 1939d4544..6069e03be 100644 --- a/schema/posit-publishing-schema-v3.json +++ b/schema/posit-publishing-schema-v3.json @@ -26,25 +26,24 @@ "type": "string", "description": "Indicates the type of content being deployed.", "enum": [ - "api", - "jupyter-static", + "html", + "jupyter-notebook", "jupyter-voila", - "python-api", "python-bokeh", "python-dash", "python-fastapi", + "python-flask", "python-shiny", "python-streamlit", "quarto-shiny", - "quarto-static", + "quarto", + "r-plumber", + "r-shiny", "rmd-shiny", - "rmd-static", - "shiny", - "static", - "tensorflow-saved-model" + "rmd" ], "examples": [ - "quarto-static" + "quarto" ] }, "entrypoint": { @@ -77,42 +76,6 @@ "images/thumbnail.jpg" ] }, - "author": { - "type": "object", - "additionalProperties": false, - "description": "Information about the author of this content.", - "properties": { - "name": { - "type": "string", - "description": "Author name.", - "examples": [ - "Michael Marchetti" - ] - }, - "email": { - "type": "string", - "format": "email", - "description": "Author email address.", - "examples": [ - "mike@posit.co" - ] - }, - "username": { - "type": "string", - "description": "Author's username on the publishing server.", - "examples": [ - "mmarchetti" - ] - }, - "organization": { - "type": "string", - "description": "Author's organization.", - "examples": [ - "Posit, PBC" - ] - } - } - }, "tags": { "type": "array", "description": "List of tags to apply to this deployment. When publishing to Connect, tags must be pre-defined by an administrator.", @@ -125,21 +88,6 @@ ] } }, - "files": { - "type": "array", - "description": "List of files to include in the deployment. The default is to include all files not matched by a pattern in an ignore file.", - "items": { - "type": "string", - "description": "List of relative file paths that are used by this deployment. When creating a bundle, these files must exist in the project directory. When deploying from git, these files must exist in the repository.", - "examples": [ - "report.qmd", - "_quarto.yml", - "model.py", - "requirements.txt", - "weights/*.hdf5" - ] - } - }, "dependencies": { "type": "object", "additionalProperties": false, @@ -155,9 +103,10 @@ "properties": { "version": { "type": "string", - "description": "Python version. The server must have a similar Python version in order to run the content.", + "description": "Python version. The server must have a matching Python version in order to run the content.", "examples": [ - "3.11.3" + "3.11.3", + "3.11" ] }, "package_file": { @@ -272,28 +221,32 @@ } ] }, - "schedule": { - "type": "object", - "additionalProperties": false, - "description": "Schedule for recurring execution of this content. Only applies to reports, such as Quarto and R Markdown.", - "required": [ - "start", - "recurrence" - ], - "properties": { - "start": { - "type": "string", - "description": "Time for the first run of the content.", - "examples": [ - "2023-10-25T08:00:00Z" - ] - }, - "recurrence": { - "type": "string", - "description": "Recurrence scheme for the content, in iCalendar RRULE format.", - "examples": [ - "FREQ=MONTHLY;INTERVAL=3" - ] + "schedules": { + "type": "array", + "description": "Schedules for recurring execution of this content. Only applies to reports, such as Quarto, R Markdown, and Jupyter Notebooks.", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "start", + "recurrence" + ], + "properties": { + "start": { + "type": "string", + "description": "Time for the first run of the content.", + "examples": [ + "2023-10-25T08:00:00Z" + ] + }, + "recurrence": { + "type": "string", + "description": "Recurrence scheme for the content, in cron or iCalendar RRULE format.", + "examples": [ + "FREQ=MONTHLY;INTERVAL=3", + "0 2 * * *" + ] + } } } }, From cb2bfe799f892b33b23a2ae5d194ba5f1f3a6211 Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Thu, 9 Nov 2023 12:57:57 -0500 Subject: [PATCH 12/18] update example toml --- schema/deploy.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/schema/deploy.toml b/schema/deploy.toml index 5e26685a2..5051aba09 100644 --- a/schema/deploy.toml +++ b/schema/deploy.toml @@ -1,5 +1,5 @@ "$schema" = "./posit-publishing-schema-v3.json" -type = "quarto-static" +type = "quarto" entrypoint = "report.qmd" title = "Regional Quarterly Sales Report" description = "This is the quarterly sales report, broken down by region." @@ -22,7 +22,7 @@ version = "1.4" [environment] API_URL = "https://example.com/api" -[schedule] +[[schedules]] start = "2023-10-25T08:00:00Z" recurrence = "FREQ=MONTHLY;INTERVAL=3" From fb702ffe78f0a477530f6300090f7d863ac848a9 Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Mon, 13 Nov 2023 15:12:11 -0500 Subject: [PATCH 13/18] updates from PR feedback --- schema/deploy.toml | 2 + schema/posit-publishing-record-schema-v3.json | 27 +++++-- schema/posit-publishing-schema-v3.json | 22 ++++-- schema/record.json | 6 -- schema/record.toml | 72 +++++++++++++++++++ 5 files changed, 112 insertions(+), 17 deletions(-) delete mode 100644 schema/record.json create mode 100644 schema/record.toml diff --git a/schema/deploy.toml b/schema/deploy.toml index 5051aba09..e62101772 100644 --- a/schema/deploy.toml +++ b/schema/deploy.toml @@ -6,6 +6,8 @@ description = "This is the quarterly sales report, broken down by region." thumbnail = "images/thumbnail.jpg" tags = [ "sales", "quarterly", "regional" ] +secrets = ["API_KEY"] + [dependencies.python] version = "3.11.3" package_file = "requirements.txt" diff --git a/schema/posit-publishing-record-schema-v3.json b/schema/posit-publishing-record-schema-v3.json index 86f2b8ae2..09f204497 100644 --- a/schema/posit-publishing-record-schema-v3.json +++ b/schema/posit-publishing-record-schema-v3.json @@ -14,12 +14,12 @@ "$schema": { "type": "string", "format": "url", - "description": "URL of the json-schema definition for this file. Must be 'https://cdn.posit.co/connect/posit-publishing-schema-v3.json'.", + "description": "URL of the json-schema definition for this file. Must be 'https://cdn.posit.co/connect/posit-publishing-record-schema-v3.json'.", "enum": [ "./posit-publishing-record-schema-v3.json" ], "examples": [ - "./posit-publishing-record-schema-v3.json" + "https://cdn.posit.co/connect/posit-publishing-record-schema-v3.json" ] }, "server-url": { @@ -37,12 +37,29 @@ "de2e7bdb-b085-401e-a65c-443e40009749" ] }, - "configuration": { + "configuration-file": { "type": "string", - "description": "Path to the configuration file within the project directory.", + "description": "Name of the configuration file that was used during deployment.", + "examples": [ + "production.toml" + ] + }, + "configuration": { + "$ref": "./posit-publishing-schema-v3.json" + }, + "files": { + "type": "array", + "items": { + "type": [ + "string" + ] + }, + "description": "Project-relative paths of the files that were included in the deployment.", "examples": [ - "production.json" + "app.py", + "model/weights.csv" ] + } } } diff --git a/schema/posit-publishing-schema-v3.json b/schema/posit-publishing-schema-v3.json index 6069e03be..6e3d79664 100644 --- a/schema/posit-publishing-schema-v3.json +++ b/schema/posit-publishing-schema-v3.json @@ -19,7 +19,7 @@ "./posit-publishing-schema-v3.json" ], "examples": [ - "./posit-publishing-schema-v3.json" + "https://cdn.posit.co/connect/posit-publishing-schema-v3.json" ] }, "type": { @@ -209,18 +209,28 @@ "type": "object", "additionalProperties": { "type": [ - "string", - "null" + "string" ] }, - "description": "Environment variable/value map. Null values may be used for secrets; deployment tools should populate these from the deploying environment. For git-based deployments, the publishing server must be configured to provide those values.", + "description": "Environment variable/value map. Secrets such as API keys or tokens should not be stored here.", "examples": [ { - "API_URL": "https://example.com/api", - "API_KEY": null + "API_URL": "https://example.com/api" } ] }, + "secrets": { + "type": "array", + "items": { + "type": [ + "string" + ] + }, + "description": "Names of secrets that should be injected as environment variables.", + "examples": [ + "API_KEY" + ] + }, "schedules": { "type": "array", "description": "Schedules for recurring execution of this content. Only applies to reports, such as Quarto, R Markdown, and Jupyter Notebooks.", diff --git a/schema/record.json b/schema/record.json deleted file mode 100644 index 8f6b1f7ce..000000000 --- a/schema/record.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "$schema": "./posit-publishing-record-schema-v3.json", - "server-url": "https://connect.example.com", - "id": "de2e7bdb-b085-401e-a65c-443e40009749", - "configuration": "production.json" -} diff --git a/schema/record.toml b/schema/record.toml new file mode 100644 index 000000000..802dd74ea --- /dev/null +++ b/schema/record.toml @@ -0,0 +1,72 @@ +"$schema" = "./posit-publishing-record-schema-v3.json" +server-url = "https://connect.example.com" +id = "de2e7bdb-b085-401e-a65c-443e40009749" +configuration-file = "production.json" + +[configuration] +"$schema" = "./posit-publishing-schema-v3.json" +type = "quarto" +entrypoint = "report.qmd" +title = "Regional Quarterly Sales Report" +description = "This is the quarterly sales report, broken down by region." +thumbnail = "images/thumbnail.jpg" +tags = [ "sales", "quarterly", "regional" ] +secrets = ["API_KEY"] + +[configuration.dependencies.python] +version = "3.11.3" +package_file = "requirements.txt" +package_manager = "pip" + +[configuration.dependencies.r] +version = "4.3.1" +package_file = "renv.lock" +package_manager = "renv" + +[configuration.dependencies.quarto] +version = "1.4" + +[configuration.environment] +API_URL = "https://example.com/api" + +[[configuration.schedules]] +start = "2023-10-25T08:00:00Z" +recurrence = "FREQ=MONTHLY;INTERVAL=3" + +[[configuration.access.users]] +id = "jqpublic" +guid = "536b456e-0311-4f92-ba10-dbf1db8a468e" +name = "John Q. Public" +permissions = "editor" + +[[configuration.access.groups]] +id = "Data Science Team" +guid = "8b4fde3e-f995-4894-bc02-ae47538262ff" +name = "Data Science Team" +permissions = "editor" + +[configuration.connect.access] +run_as = "rstudio-connect" +run_as_current_user = false + +[configuration.connect.runtime] +connection_timeout = 5 +read_timeout = 30 +init_timeout = 60 +idle_timeout = 120 +max_processes = 5 +min_processes = 1 +max_conns_per_process = 50 +load_factor = 0.5 + +[configuration.connect.kubernetes] +amd_gpu_limit = 0 +cpu_limit = 1 +cpu_request = 0.5 +image_name = "posit/connect-runtime-python3.11-r4.3" +memory_limit = "100000000" +memory_request = "20000000" +nvidia_gpu_limit = 0 +service_account_name = "posit-connect-content" +r_environment_management = true +py_environment_management = true From 1e2561be215b5a894b405d2f48aa69bb87819644 Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Tue, 14 Nov 2023 10:39:47 -0500 Subject: [PATCH 14/18] standardize on hyphenation --- schema/posit-publishing-schema-v3.json | 56 +++++++++++++------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/schema/posit-publishing-schema-v3.json b/schema/posit-publishing-schema-v3.json index 6e3d79664..2b54042a8 100644 --- a/schema/posit-publishing-schema-v3.json +++ b/schema/posit-publishing-schema-v3.json @@ -109,7 +109,7 @@ "3.11" ] }, - "package_file": { + "package-file": { "type": "string", "description": "File containing package dependencies. The file must exist and be listed under 'files'.", "default": "requirements.txt", @@ -117,7 +117,7 @@ "requirements.txt" ] }, - "package_manager": { + "package-manager": { "type": "string", "default": "pip", "enum": [ @@ -127,7 +127,7 @@ "poetry", "none" ], - "description": "Package manager that will install the dependencies. If package_manager is none, dependencies will not be installed.", + "description": "Package manager that will install the dependencies. If package-manager is none, dependencies will not be installed.", "examples": [ "pip" ] @@ -149,7 +149,7 @@ "4.3.1" ] }, - "package_file": { + "package-file": { "type": "string", "default": "renv.lock", "description": "File containing package dependencies. The file must exist and be listed under 'files'.", @@ -157,14 +157,14 @@ "renv.lock" ] }, - "package_manager": { + "package-manager": { "type": "string", "default": "renv", "enum": [ "renv", "none" ], - "description": "Package manager that will install the dependencies. If package_manager is none, dependencies will not be installed.", + "description": "Package manager that will install the dependencies. If package-manager is none, dependencies will not be installed.", "examples": [ "renv" ] @@ -298,7 +298,7 @@ }, "guid": { "type": "string", - "description": "Unique identifier assigned by Connect. When deploying to the same server, this will determine the user who is granted access. When deploying to a different server, the provider_id will be used to look up the user.", + "description": "Unique identifier assigned by Connect. When deploying to the same server, this will determine the user who is granted access. When deploying to a different server, the provider-id will be used to look up the user.", "examples": [ "cd2e7cef-a195-512e-b76d-554f4f5a239c" ] @@ -341,7 +341,7 @@ }, "guid": { "type": "string", - "description": "Unique identifier assigned by Connect. When deploying to the same server, this will determine the user who is granted access. When deploying to a different server, the provider_id will be used to look up the user.", + "description": "Unique identifier assigned by Connect. When deploying to the same server, this will determine the user who is granted access. When deploying to a different server, the provider-id will be used to look up the user.", "examples": [ "8b4fde3e-f995-4894-bc02-ae47538262ff" ] @@ -373,14 +373,14 @@ "description": "Setting specific to Posit Connect deployments.", "properties": { "access": { - "run_as": { + "run-as": { "type": "string", "description": "The system username under which the content should be run. Must be an existing user in the allowed group. You must be an administrator to set this value.", "examples": [ "rstudio-connect" ] }, - "run_as_current_user": { + "run-as-current-user": { "type": "boolean", "default": false, "description": "For application content types, run a separate process for each visiting user under that user's server account. Requires PAM authentication on the Posit Connect server. You must be an administrator to set this value." @@ -391,7 +391,7 @@ "additionalProperties": false, "description": "Runtime settings for application content types.", "properties": { - "connection_timeout": { + "connection-timeout": { "type": "integer", "minimum": 0, "description": "Maximum number of seconds allowed without data sent or received across a client connection. A value of `0` means connections will never time-out (not recommended).", @@ -399,7 +399,7 @@ 5 ] }, - "read_timeout": { + "read-timeout": { "type": "integer", "minimum": 0, "description": "Maximum number of seconds allowed without data received from a client connection. A value of `0` means a lack of client (browser) interaction never causes the connection to close.", @@ -407,21 +407,21 @@ 30 ] }, - "init_timeout": { + "init-timeout": { "type": "integer", "description": "The maximum number of seconds allowed for an interactive application to start. Posit Connect must be able to connect to a newly launched application before this threshold has elapsed.", "examples": [ 60 ] }, - "idle_timeout": { + "idle-timeout": { "type": "integer", "description": "The maximum number of seconds a worker process for an interactive application to remain alive after it goes idle (no active connections).", "examples": [ 120 ] }, - "max_processes": { + "max-processes": { "type": "integer", "minimum": 1, "description": "Specifies the total number of concurrent processes allowed for a single interactive application.", @@ -429,7 +429,7 @@ 5 ] }, - "min_processes": { + "min-processes": { "type": "integer", "minimum": 0, "description": "Specifies the minimum number of concurrent processes allowed for a single interactive application.", @@ -437,7 +437,7 @@ 1 ] }, - "max_conns_per_process": { + "max-conns-per-process": { "type": "integer", "minimum": 1, "description": "Specifies the maximum number of client connections allowed to an individual process. Incoming connections which will exceed this limit are routed to a new process or rejected.", @@ -445,7 +445,7 @@ 50 ] }, - "load_factor": { + "load-factor": { "type": "number", "minimum": 0, "maximum": 1, @@ -461,70 +461,70 @@ "additionalProperties": false, "description": "Settings used with Posit Connect's off-host execution feature, where content is run in Kubernetes.", "properties": { - "amd_gpu_limit": { + "amd-gpu-limit": { "type": "integer", "description": "The number of AMD GPUs that will be allocated by Kubernetes to run this content.", "examples": [ 0 ] }, - "cpu_limit": { + "cpu-limit": { "type": "integer", "description": "The maximum amount of compute power this content will be allowed to consume when executing or rendering, expressed in CPU Units, where 1.0 unit is equivalent to 1 physical or virtual core. Fractional values are allowed. If the process tries to use more CPU than allowed, it will be throttled.", "examples": [ 1 ] }, - "cpu_request": { + "cpu-request": { "type": "number", "description": "The minimum amount of compute power this content needs when executing virtual core. Fractional values are allowed.", "examples": [ 0.5 ] }, - "image_name": { + "image-name": { "type": "string", "description": "Name of the target container image.", "examples": [ "posit/connect-runtime-python3.11-r4.3" ] }, - "memory_limit": { + "memory-limit": { "type": "string", "description": "The maximum amount of RAM this content will be allowed to consume when executing or rendering, expressed in bytes. If the process tries to use more memory than allowed, it will be terminated", "examples": [ "100000000" ] }, - "memory_request": { + "memory-request": { "type": "string", "description": "The minimum amount of RAM this content needs when executing or rendering, expressed in bytes.", "examples": [ "20000000" ] }, - "nvidia_gpu_limit": { + "nvidia-gpu-limit": { "type": "integer", "description": "The number of NVIDIA GPUs that will be allocated by Kubernetes to run this content.", "examples": [ 0 ] }, - "service_account_name": { + "service-account-name": { "type": "string", "description": "The name of the Kubernetes service account that is used to run this content. It must adhere to Kubernetes service account naming rules. You must be an administrator to set this value.", "examples": [ "posit-connect-content" ] }, - "r_environment_management": { + "r-environment-management": { "type": "boolean", "description": "Enables or disables R environment management. When false, Posit Connect will not install R packages and instead assume that all required packages are present in the container image.", "examples": [ true ] }, - "py_environment_management": { + "py-environment-management": { "type": "boolean", "description": "Enables or disables Python environment management. When false, Posit Connect will not install Python packages and instead assume that all required packages are present in the container image.", "examples": [ From 0312e3faa8402ec91b00ed56d997e6b6e863f29a Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Tue, 14 Nov 2023 12:31:46 -0500 Subject: [PATCH 15/18] break dependencies out of subsection --- schema/deploy.toml | 54 +++---- schema/posit-publishing-schema-v3.json | 210 ++++++++++++------------- 2 files changed, 128 insertions(+), 136 deletions(-) diff --git a/schema/deploy.toml b/schema/deploy.toml index e62101772..a125a1612 100644 --- a/schema/deploy.toml +++ b/schema/deploy.toml @@ -8,17 +8,17 @@ tags = [ "sales", "quarterly", "regional" ] secrets = ["API_KEY"] -[dependencies.python] +[python] version = "3.11.3" -package_file = "requirements.txt" -package_manager = "pip" +package-file = "requirements.txt" +package-manager = "pip" -[dependencies.r] +[r] version = "4.3.1" -package_file = "renv.lock" -package_manager = "renv" +package-file = "renv.lock" +package-manager = "renv" -[dependencies.quarto] +[quarto] version = "1.4" [environment] @@ -41,27 +41,27 @@ name = "Data Science Team" permissions = "editor" [connect.access] -run_as = "rstudio-connect" -run_as_current_user = false +run-as = "rstudio-connect" +run-as-current-user = false [connect.runtime] -connection_timeout = 5 -read_timeout = 30 -init_timeout = 60 -idle_timeout = 120 -max_processes = 5 -min_processes = 1 -max_conns_per_process = 50 -load_factor = 0.5 +connection-timeout = 5 +read-timeout = 30 +init-timeout = 60 +idle-timeout = 120 +max-processes = 5 +min-processes = 1 +max-connections = 50 +load-factor = 0.5 [connect.kubernetes] -amd_gpu_limit = 0 -cpu_limit = 1 -cpu_request = 0.5 -image_name = "posit/connect-runtime-python3.11-r4.3" -memory_limit = "100000000" -memory_request = "20000000" -nvidia_gpu_limit = 0 -service_account_name = "posit-connect-content" -r_environment_management = true -py_environment_management = true +amd-gpu-limit = 0 +cpu-limit = 1 +cpu-request = 0.5 +image-name = "posit/connect-runtime-python3.11-r4.3" +memory-limit = "100000000" +memory-request = "20000000" +nvidia-gpu-limit = 0 +service-account-name = "posit-connect-content" +r-environment-management = true +py-environment-management = true diff --git a/schema/posit-publishing-schema-v3.json b/schema/posit-publishing-schema-v3.json index 2b54042a8..90a2a33fb 100644 --- a/schema/posit-publishing-schema-v3.json +++ b/schema/posit-publishing-schema-v3.json @@ -7,8 +7,7 @@ "required": [ "$schema", "type", - "entrypoint", - "dependencies" + "entrypoint" ], "properties": { "$schema": { @@ -88,119 +87,112 @@ ] } }, - "dependencies": { + "python": { "type": "object", "additionalProperties": false, - "description": "Language runtimes and packages needed to run this content.", + "description": "Python language and dependencies.", + "required": [ + "version" + ], "properties": { - "python": { - "type": "object", - "additionalProperties": false, - "description": "Python language and dependencies.", - "required": [ - "version" - ], - "properties": { - "version": { - "type": "string", - "description": "Python version. The server must have a matching Python version in order to run the content.", - "examples": [ - "3.11.3", - "3.11" - ] - }, - "package-file": { - "type": "string", - "description": "File containing package dependencies. The file must exist and be listed under 'files'.", - "default": "requirements.txt", - "examples": [ - "requirements.txt" - ] - }, - "package-manager": { - "type": "string", - "default": "pip", - "enum": [ - "pip", - "conda", - "pipenv", - "poetry", - "none" - ], - "description": "Package manager that will install the dependencies. If package-manager is none, dependencies will not be installed.", - "examples": [ - "pip" - ] - } - } + "version": { + "type": "string", + "description": "Python version. The server must have a matching Python version in order to run the content.", + "examples": [ + "3.11.3", + "3.11" + ] }, - "r": { - "type": "object", - "additionalProperties": false, - "description": "R language and dependencies.", - "required": [ - "version" + "package-file": { + "type": "string", + "description": "File containing package dependencies. The file must exist and be listed under 'files'.", + "default": "requirements.txt", + "examples": [ + "requirements.txt" + ] + }, + "package-manager": { + "type": "string", + "default": "pip", + "enum": [ + "pip", + "conda", + "pipenv", + "poetry", + "none" ], - "properties": { - "version": { - "type": "string", - "description": "R version. The server must have a similar R version in order to run the content.", - "examples": [ - "4.3.1" - ] - }, - "package-file": { - "type": "string", - "default": "renv.lock", - "description": "File containing package dependencies. The file must exist and be listed under 'files'.", - "examples": [ - "renv.lock" - ] - }, - "package-manager": { - "type": "string", - "default": "renv", - "enum": [ - "renv", - "none" - ], - "description": "Package manager that will install the dependencies. If package-manager is none, dependencies will not be installed.", - "examples": [ - "renv" - ] - } - } + "description": "Package manager that will install the dependencies. If package-manager is none, dependencies will not be installed.", + "examples": [ + "pip" + ] + } + } + }, + "r": { + "type": "object", + "additionalProperties": false, + "description": "R language and dependencies.", + "required": [ + "version" + ], + "properties": { + "version": { + "type": "string", + "description": "R version. The server must have a similar R version in order to run the content.", + "examples": [ + "4.3.1" + ] }, - "quarto": { - "type": "object", - "additionalProperties": false, - "description": "Quarto version required to run the content.", - "required": [ - "version" + "package-file": { + "type": "string", + "default": "renv.lock", + "description": "File containing package dependencies. The file must exist and be listed under 'files'.", + "examples": [ + "renv.lock" + ] + }, + "package-manager": { + "type": "string", + "default": "renv", + "enum": [ + "renv", + "none" ], - "properties": { - "version": { - "type": "string", - "description": "Quarto version. The server must have a similar Quarto version in order to run the content.", - "examples": [ - "1.4" - ] - }, - "engines": { - "type": "array", - "description": "List of Quarto engines required for this content.", - "items": { - "type": "string", - "enum": [ - "knitr", - "jupyter" - ], - "examples": [ - "knitr", - "jupyter" - ] - } - } + "description": "Package manager that will install the dependencies. If package-manager is none, dependencies will not be installed.", + "examples": [ + "renv" + ] + } + } + }, + "quarto": { + "type": "object", + "additionalProperties": false, + "description": "Quarto version required to run the content.", + "required": [ + "version" + ], + "properties": { + "version": { + "type": "string", + "description": "Quarto version. The server must have a similar Quarto version in order to run the content.", + "examples": [ + "1.4" + ] + }, + "engines": { + "type": "array", + "description": "List of Quarto engines required for this content.", + "items": { + "type": "string", + "enum": [ + "knitr", + "jupyter" + ], + "examples": [ + "knitr", + "jupyter" + ] } } } @@ -437,7 +429,7 @@ 1 ] }, - "max-conns-per-process": { + "max-connections": { "type": "integer", "minimum": 1, "description": "Specifies the maximum number of client connections allowed to an individual process. Incoming connections which will exceed this limit are routed to a new process or rejected.", From fbd5a5bf521bb2c5cc70368dedf88d855c5a19e9 Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Tue, 28 Nov 2023 15:02:36 -0500 Subject: [PATCH 16/18] map config types to Connect app modes --- cmd/connect-client/commands/init.go | 11 +++-- internal/bundles/manifest.go | 30 +++++++++++-- internal/config/config_test.go | 3 +- internal/config/types.go | 65 +++++++++++++++++++++-------- internal/inspect/all_test.go | 10 ++--- internal/inspect/html.go | 4 +- internal/inspect/html_test.go | 8 ++-- internal/inspect/infer.go | 4 +- internal/inspect/notebook.go | 6 +-- internal/inspect/notebook_test.go | 6 +-- internal/inspect/python.go | 24 +++++------ internal/inspect/python_test.go | 8 ++-- 12 files changed, 115 insertions(+), 64 deletions(-) diff --git a/cmd/connect-client/commands/init.go b/cmd/connect-client/commands/init.go index 33de82d49..8d9127386 100644 --- a/cmd/connect-client/commands/init.go +++ b/cmd/connect-client/commands/init.go @@ -5,7 +5,6 @@ package commands import ( "fmt" - "github.com/rstudio/connect-client/internal/apptypes" "github.com/rstudio/connect-client/internal/bundles" "github.com/rstudio/connect-client/internal/cli_types" "github.com/rstudio/connect-client/internal/config" @@ -22,7 +21,7 @@ func (cmd *InitCommand) inspectProjectType(log logging.Logger) (*inspect.Content if err != nil { return nil, fmt.Errorf("error detecting content type: %w", err) } - log.Info("Deployment type", "Entrypoint", contentType.Entrypoint, "AppMode", contentType.AppMode) + log.Info("Deployment type", "Entrypoint", contentType.Entrypoint, "AppMode", contentType.Type) return contentType, nil } @@ -33,8 +32,8 @@ type InitCommand struct { config *config.Config } -func (cmd *InitCommand) requiresPython(appMode apptypes.AppMode) (bool, error) { - if appMode.IsPythonContent() { +func (cmd *InitCommand) requiresPython(contentType config.ContentType) (bool, error) { + if contentType.IsPythonContent() { return true, nil } if cmd.Python.Path() != "" { @@ -79,10 +78,10 @@ func (cmd *InitCommand) Run(args *cli_types.CommonArgs, ctx *cli_types.CLIContex if err != nil { return err } - cmd.config.Type = contentType.AppMode + cmd.config.Type = contentType.Type cmd.config.Entrypoint = contentType.Entrypoint - requiresPython, err := cmd.requiresPython(contentType.AppMode) + requiresPython, err := cmd.requiresPython(contentType.Type) if err != nil { return err } diff --git a/internal/bundles/manifest.go b/internal/bundles/manifest.go index d27d7cab7..6a2dea8ca 100644 --- a/internal/bundles/manifest.go +++ b/internal/bundles/manifest.go @@ -154,11 +154,33 @@ func NewManifest() *Manifest { } } +var connectContentTypeMap = map[config.ContentType]apptypes.AppMode{ + config.ContentTypeHTML: apptypes.StaticMode, + config.ContentTypeJupyterNotebook: apptypes.StaticJupyterMode, + config.ContentTypeJupyterVoila: apptypes.JupyterVoilaMode, + config.ContentTypePythonBokeh: apptypes.PythonBokehMode, + config.ContentTypePythonDash: apptypes.PythonDashMode, + config.ContentTypePythonFastAPI: apptypes.PythonFastAPIMode, + config.ContentTypePythonFlask: apptypes.PythonAPIMode, + config.ContentTypePythonShiny: apptypes.PythonShinyMode, + config.ContentTypePythonStreamlit: apptypes.PythonStreamlitMode, + config.ContentTypeQuartoShiny: apptypes.ShinyQuartoMode, + config.ContentTypeQuarto: apptypes.StaticQuartoMode, + config.ContentTypeRPlumber: apptypes.PlumberAPIMode, + config.ContentTypeRShiny: apptypes.ShinyMode, + config.ContentTypeRMarkdownShiny: apptypes.ShinyRmdMode, + config.ContentTypeRMarkdown: apptypes.StaticRmdMode, +} + func NewManifestFromConfig(cfg *config.Config) *Manifest { + contentType, ok := connectContentTypeMap[cfg.Type] + if !ok { + contentType = apptypes.UnknownMode + } m := &Manifest{ Version: 1, Metadata: Metadata{ - AppMode: cfg.Type, + AppMode: contentType, Entrypoint: cfg.Entrypoint, }, Jupyter: nil, @@ -185,10 +207,10 @@ func NewManifestFromConfig(cfg *config.Config) *Manifest { } } switch cfg.Type { - case apptypes.StaticRmdMode: - case apptypes.ShinyRmdMode: + case config.ContentTypeRMarkdown: + case config.ContentTypeRMarkdownShiny: m.Metadata.PrimaryRmd = cfg.Entrypoint - case apptypes.StaticMode: + case config.ContentTypeHTML: m.Metadata.PrimaryHtml = cfg.Entrypoint } return m diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 1e7e70680..6846c1778 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -6,7 +6,6 @@ import ( "io/fs" "testing" - "github.com/rstudio/connect-client/internal/apptypes" "github.com/rstudio/connect-client/internal/util" "github.com/rstudio/connect-client/internal/util/utiltest" "github.com/spf13/afero" @@ -60,7 +59,7 @@ func (s *ConfigSuite) TestFromFile() { cfg, err := FromFile(path) s.NoError(err) s.NotNil(cfg) - s.Equal(apptypes.AppMode("python-dash"), cfg.Type) + s.Equal(ContentTypePythonDash, cfg.Type) } func (s *ConfigSuite) TestFromFileErr() { diff --git a/internal/config/types.go b/internal/config/types.go index 9e75413b5..1b45c678a 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -2,26 +2,57 @@ package config // Copyright (C) 2023 by Posit Software, PBC. -import ( - "github.com/rstudio/connect-client/internal/apptypes" +type ContentType string + +const ( + ContentTypeHTML ContentType = "html" + ContentTypeJupyterNotebook ContentType = "jupyter-notebook" + ContentTypeJupyterVoila ContentType = "jupyter-voila" + ContentTypePythonBokeh ContentType = "python-bokeh" + ContentTypePythonDash ContentType = "python-dash" + ContentTypePythonFastAPI ContentType = "python-fastapi" + ContentTypePythonFlask ContentType = "python-flask" + ContentTypePythonShiny ContentType = "python-shiny" + ContentTypePythonStreamlit ContentType = "python-streamlit" + ContentTypeQuartoShiny ContentType = "quarto-shiny" + ContentTypeQuarto ContentType = "quarto" + ContentTypeRPlumber ContentType = "r-plumber" + ContentTypeRShiny ContentType = "r-shiny" + ContentTypeRMarkdownShiny ContentType = "rmd-shiny" + ContentTypeRMarkdown ContentType = "rmd" ) +func (t ContentType) IsPythonContent() bool { + switch t { + case ContentTypeJupyterNotebook: + case ContentTypeJupyterVoila: + case ContentTypePythonBokeh: + case ContentTypePythonDash: + case ContentTypePythonFastAPI: + case ContentTypePythonFlask: + case ContentTypePythonShiny: + case ContentTypePythonStreamlit: + return true + } + return false +} + type Config struct { - Schema SchemaURL `toml:"$schema" json:"$schema"` - Type apptypes.AppMode `toml:"type" json:"type"` - Entrypoint string `toml:"entrypoint,omitempty" json:"entrypoint,omitempty"` - Title string `toml:"title,omitempty" json:"title,omitempty"` - Description string `toml:"description,multiline,omitempty" json:"description,omitempty"` - ThumbnailFile string `toml:"thumbnail,omitempty" json:"thumbnail,omitempty"` - Tags []string `toml:"tags,omitempty" json:"tags,omitempty"` - Python *Python `toml:"python,omitempty" json:"python,omitempty"` - R *R `toml:"r,omitempty" json:"r,omitempty"` - Quarto *Quarto `toml:"quarto,omitempty" json:"quarto,omitempty"` - Environment Environment `toml:"environment,omitempty" json:"environment,omitempty"` - Secrets []string `toml:"secrets,omitempty" json:"secrets,omitempty"` - Schedules []Schedule `toml:"schedules,omitempty" json:"schedules,omitempty"` - Access *Access `toml:"access,omitempty" json:"access,omitempty"` - Connect *Connect `toml:"connect,omitempty" json:"connect,omitempty"` + Schema SchemaURL `toml:"$schema" json:"$schema"` + Type ContentType `toml:"type" json:"type"` + Entrypoint string `toml:"entrypoint,omitempty" json:"entrypoint,omitempty"` + Title string `toml:"title,omitempty" json:"title,omitempty"` + Description string `toml:"description,multiline,omitempty" json:"description,omitempty"` + ThumbnailFile string `toml:"thumbnail,omitempty" json:"thumbnail,omitempty"` + Tags []string `toml:"tags,omitempty" json:"tags,omitempty"` + Python *Python `toml:"python,omitempty" json:"python,omitempty"` + R *R `toml:"r,omitempty" json:"r,omitempty"` + Quarto *Quarto `toml:"quarto,omitempty" json:"quarto,omitempty"` + Environment Environment `toml:"environment,omitempty" json:"environment,omitempty"` + Secrets []string `toml:"secrets,omitempty" json:"secrets,omitempty"` + Schedules []Schedule `toml:"schedules,omitempty" json:"schedules,omitempty"` + Access *Access `toml:"access,omitempty" json:"access,omitempty"` + Connect *Connect `toml:"connect,omitempty" json:"connect,omitempty"` } type SchemaURL string diff --git a/internal/inspect/all_test.go b/internal/inspect/all_test.go index 6e2f7ca75..44de407b1 100644 --- a/internal/inspect/all_test.go +++ b/internal/inspect/all_test.go @@ -6,7 +6,7 @@ import ( "os" "testing" - "github.com/rstudio/connect-client/internal/apptypes" + "github.com/rstudio/connect-client/internal/config" "github.com/rstudio/connect-client/internal/util" "github.com/rstudio/connect-client/internal/util/utiltest" "github.com/spf13/afero" @@ -35,7 +35,7 @@ func (s *AllSuite) TestInferTypeDirectory() { t, err := detector.InferType(path) s.Nil(err) s.Equal(&ContentType{ - AppMode: apptypes.PythonDashMode, + Type: config.ContentTypePythonDash, Entrypoint: appFilename, RequiresPython: true, }, t) @@ -57,7 +57,7 @@ func (s *AllSuite) TestInferTypeFileLowerPriority() { t, err := detector.InferType(htmlPath) s.Nil(err) s.Equal(&ContentType{ - AppMode: apptypes.StaticMode, + Type: config.ContentTypeHTML, Entrypoint: htmlFilename, }, t) } @@ -78,7 +78,7 @@ func (s *AllSuite) TestInferTypeFileHigherPriority() { t, err := detector.InferType(appPath) s.Nil(err) s.Equal(&ContentType{ - AppMode: apptypes.PythonDashMode, + Type: config.ContentTypePythonDash, Entrypoint: appFilename, RequiresPython: true, }, t) @@ -99,7 +99,7 @@ func (s *AllSuite) TestInferTypeDirectoryPriority() { t, err := detector.InferType(path) s.Nil(err) s.Equal(&ContentType{ - AppMode: apptypes.PythonDashMode, + Type: config.ContentTypePythonDash, Entrypoint: appFilename, RequiresPython: true, }, t) diff --git a/internal/inspect/html.go b/internal/inspect/html.go index 7f36e0ad3..a7e3bfdc9 100644 --- a/internal/inspect/html.go +++ b/internal/inspect/html.go @@ -3,7 +3,7 @@ package inspect // Copyright (C) 2023 by Posit Software, PBC. import ( - "github.com/rstudio/connect-client/internal/apptypes" + "github.com/rstudio/connect-client/internal/config" "github.com/rstudio/connect-client/internal/util" ) @@ -30,7 +30,7 @@ func (d *StaticHTMLDetector) InferType(path util.Path) (*ContentType, error) { } if entrypoint != "" { return &ContentType{ - AppMode: apptypes.StaticMode, + Type: config.ContentTypeHTML, Entrypoint: entrypoint, }, nil } diff --git a/internal/inspect/html_test.go b/internal/inspect/html_test.go index ce57b1e1c..44af8af93 100644 --- a/internal/inspect/html_test.go +++ b/internal/inspect/html_test.go @@ -6,7 +6,7 @@ import ( "errors" "testing" - "github.com/rstudio/connect-client/internal/apptypes" + "github.com/rstudio/connect-client/internal/config" "github.com/rstudio/connect-client/internal/util" "github.com/rstudio/connect-client/internal/util/utiltest" "github.com/spf13/afero" @@ -32,7 +32,7 @@ func (s *StaticHTMLDetectorSuite) TestInferTypeSpecifiedFile() { t, err := detector.InferType(path) s.Nil(err) s.Equal(&ContentType{ - AppMode: apptypes.StaticMode, + Type: config.ContentTypeHTML, Entrypoint: filename, }, t) } @@ -47,7 +47,7 @@ func (s *StaticHTMLDetectorSuite) TestInferTypePreferredFilename() { t, err := detector.InferType(path) s.Nil(err) s.Equal(&ContentType{ - AppMode: apptypes.StaticMode, + Type: config.ContentTypeHTML, Entrypoint: filename, }, t) } @@ -62,7 +62,7 @@ func (s *StaticHTMLDetectorSuite) TestInferTypeOnlyHTMLFile() { t, err := detector.InferType(path) s.Nil(err) s.Equal(&ContentType{ - AppMode: apptypes.StaticMode, + Type: config.ContentTypeHTML, Entrypoint: filename, }, t) } diff --git a/internal/inspect/infer.go b/internal/inspect/infer.go index ffbc30b98..4635b703f 100644 --- a/internal/inspect/infer.go +++ b/internal/inspect/infer.go @@ -5,12 +5,12 @@ package inspect import ( "io" - "github.com/rstudio/connect-client/internal/apptypes" + "github.com/rstudio/connect-client/internal/config" "github.com/rstudio/connect-client/internal/util" ) type ContentType struct { - AppMode apptypes.AppMode + Type config.ContentType Entrypoint string RequiresR bool RequiresPython bool diff --git a/internal/inspect/notebook.go b/internal/inspect/notebook.go index c46fcdd51..2c2769466 100644 --- a/internal/inspect/notebook.go +++ b/internal/inspect/notebook.go @@ -8,7 +8,7 @@ import ( "io" "strings" - "github.com/rstudio/connect-client/internal/apptypes" + "github.com/rstudio/connect-client/internal/config" "github.com/rstudio/connect-client/internal/util" ) @@ -53,9 +53,9 @@ func (d *NotebookDetector) InferType(path util.Path) (*ContentType, error) { RequiresPython: true, } if isVoila { - t.AppMode = apptypes.JupyterVoilaMode + t.Type = config.ContentTypeJupyterVoila } else { - t.AppMode = apptypes.StaticJupyterMode + t.Type = config.ContentTypeJupyterNotebook } return t, nil } diff --git a/internal/inspect/notebook_test.go b/internal/inspect/notebook_test.go index 4d4556660..c9b1a450d 100644 --- a/internal/inspect/notebook_test.go +++ b/internal/inspect/notebook_test.go @@ -10,7 +10,7 @@ import ( "strings" "testing" - "github.com/rstudio/connect-client/internal/apptypes" + "github.com/rstudio/connect-client/internal/config" "github.com/rstudio/connect-client/internal/util" "github.com/rstudio/connect-client/internal/util/utiltest" "github.com/spf13/afero" @@ -100,7 +100,7 @@ func (s *NotebookDetectorSuite) TestInferTypePlainNotebook() { t, err := detector.InferType(path) s.Nil(err) s.Equal(&ContentType{ - AppMode: apptypes.StaticJupyterMode, + Type: config.ContentTypeJupyterNotebook, Entrypoint: filename, RequiresPython: true, }, t) @@ -116,7 +116,7 @@ func (s *NotebookDetectorSuite) TestInferTypeVoilaNotebook() { t, err := detector.InferType(path) s.Nil(err) s.Equal(&ContentType{ - AppMode: apptypes.JupyterVoilaMode, + Type: config.ContentTypeJupyterVoila, Entrypoint: filename, RequiresPython: true, }, t) diff --git a/internal/inspect/python.go b/internal/inspect/python.go index 586253ddb..95f456650 100644 --- a/internal/inspect/python.go +++ b/internal/inspect/python.go @@ -3,26 +3,26 @@ package inspect // Copyright (C) 2023 by Posit Software, PBC. import ( - "github.com/rstudio/connect-client/internal/apptypes" + "github.com/rstudio/connect-client/internal/config" "github.com/rstudio/connect-client/internal/util" ) type PythonAppDetector struct { inferenceHelper - appMode apptypes.AppMode - imports []string + contentType config.ContentType + imports []string } -func NewPythonAppDetector(appMode apptypes.AppMode, imports []string) *PythonAppDetector { +func NewPythonAppDetector(contentType config.ContentType, imports []string) *PythonAppDetector { return &PythonAppDetector{ inferenceHelper: defaultInferenceHelper{}, - appMode: appMode, + contentType: contentType, imports: imports, } } func NewFlaskDetector() *PythonAppDetector { - return NewPythonAppDetector(apptypes.PythonAPIMode, []string{ + return NewPythonAppDetector(config.ContentTypePythonFlask, []string{ "flask", // also matches flask_api, flask_openapi3, etc. "flasgger", "falcon", // must check for this after falcon.asgi (FastAPI) @@ -30,7 +30,7 @@ func NewFlaskDetector() *PythonAppDetector { } func NewFastAPIDetector() *PythonAppDetector { - return NewPythonAppDetector(apptypes.PythonFastAPIMode, []string{ + return NewPythonAppDetector(config.ContentTypePythonFastAPI, []string{ "fastapi", "falcon.asgi", "quart", @@ -41,25 +41,25 @@ func NewFastAPIDetector() *PythonAppDetector { } func NewDashDetector() *PythonAppDetector { - return NewPythonAppDetector(apptypes.PythonDashMode, []string{ + return NewPythonAppDetector(config.ContentTypePythonDash, []string{ "dash", // also matches dash_core_components, dash_bio, etc. }) } func NewStreamlitDetector() *PythonAppDetector { - return NewPythonAppDetector(apptypes.PythonStreamlitMode, []string{ + return NewPythonAppDetector(config.ContentTypePythonStreamlit, []string{ "streamlit", }) } func NewBokehDetector() *PythonAppDetector { - return NewPythonAppDetector(apptypes.PythonBokehMode, []string{ + return NewPythonAppDetector(config.ContentTypePythonBokeh, []string{ "bokeh", }) } func NewPyShinyDetector() *PythonAppDetector { - return NewPythonAppDetector(apptypes.PythonShinyMode, []string{ + return NewPythonAppDetector(config.ContentTypePythonShiny, []string{ "shiny", }) } @@ -77,7 +77,7 @@ func (d *PythonAppDetector) InferType(path util.Path) (*ContentType, error) { if matches { return &ContentType{ Entrypoint: entrypoint, - AppMode: d.appMode, + Type: d.contentType, RequiresPython: true, }, nil } diff --git a/internal/inspect/python_test.go b/internal/inspect/python_test.go index bff3443d6..c6bc49d4b 100644 --- a/internal/inspect/python_test.go +++ b/internal/inspect/python_test.go @@ -6,7 +6,7 @@ import ( "errors" "testing" - "github.com/rstudio/connect-client/internal/apptypes" + "github.com/rstudio/connect-client/internal/config" "github.com/rstudio/connect-client/internal/util" "github.com/rstudio/connect-client/internal/util/utiltest" "github.com/spf13/afero" @@ -32,7 +32,7 @@ func (s *PythonSuite) TestInferTypeSpecifiedFile() { t, err := detector.InferType(path) s.Nil(err) s.Equal(&ContentType{ - AppMode: apptypes.PythonAPIMode, + Type: config.ContentTypePythonFlask, Entrypoint: filename, RequiresPython: true, }, t) @@ -48,7 +48,7 @@ func (s *PythonSuite) TestInferTypePreferredFilename() { t, err := detector.InferType(path) s.Nil(err) s.Equal(&ContentType{ - AppMode: apptypes.PythonAPIMode, + Type: config.ContentTypePythonFlask, Entrypoint: filename, RequiresPython: true, }, t) @@ -64,7 +64,7 @@ func (s *PythonSuite) TestInferTypeOnlyPythonFile() { t, err := detector.InferType(path) s.Nil(err) s.Equal(&ContentType{ - AppMode: apptypes.PythonAPIMode, + Type: config.ContentTypePythonFlask, Entrypoint: filename, RequiresPython: true, }, t) From 0f3c9ba1b40bcbb01a2444697226a98c0c07b090 Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Tue, 28 Nov 2023 15:54:33 -0500 Subject: [PATCH 17/18] update to configuration-name --- schema/posit-publishing-record-schema-v3.json | 8 ++++---- schema/posit-publishing-schema-v3.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/schema/posit-publishing-record-schema-v3.json b/schema/posit-publishing-record-schema-v3.json index 09f204497..3701e9f8c 100644 --- a/schema/posit-publishing-record-schema-v3.json +++ b/schema/posit-publishing-record-schema-v3.json @@ -8,7 +8,7 @@ "$schema", "server-url", "id", - "configuration" + "configuration-name" ], "properties": { "$schema": { @@ -37,11 +37,11 @@ "de2e7bdb-b085-401e-a65c-443e40009749" ] }, - "configuration-file": { + "configuration-name": { "type": "string", - "description": "Name of the configuration file that was used during deployment.", + "description": "Name of the configuration that was used during deployment.", "examples": [ - "production.toml" + "production" ] }, "configuration": { diff --git a/schema/posit-publishing-schema-v3.json b/schema/posit-publishing-schema-v3.json index 90a2a33fb..faa3b29c0 100644 --- a/schema/posit-publishing-schema-v3.json +++ b/schema/posit-publishing-schema-v3.json @@ -3,7 +3,7 @@ "$id": "https://github.com/rstudio/publishing-client/posit-publishing-schema-v3.json", "type": "object", "additionalProperties": false, - "description": "Posit Deployment Settings", + "description": "Posit Publishing Configuration", "required": [ "$schema", "type", From 4225f09dd8fdce22d517893cb2f063e7a090ff0c Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Wed, 29 Nov 2023 09:43:28 -0500 Subject: [PATCH 18/18] separate draft from implemented schema --- schema/deploy.toml | 20 - schema/draft/deploy.toml | 67 +++ .../posit-publishing-record-schema-v3.json | 65 +++ schema/draft/posit-publishing-schema-v3.json | 531 ++++++++++++++++++ schema/draft/record.toml | 72 +++ schema/posit-publishing-schema-v3.json | 167 ------ schema/record.toml | 30 +- 7 files changed, 739 insertions(+), 213 deletions(-) create mode 100644 schema/draft/deploy.toml create mode 100644 schema/draft/posit-publishing-record-schema-v3.json create mode 100644 schema/draft/posit-publishing-schema-v3.json create mode 100644 schema/draft/record.toml diff --git a/schema/deploy.toml b/schema/deploy.toml index a125a1612..a6e75db99 100644 --- a/schema/deploy.toml +++ b/schema/deploy.toml @@ -3,10 +3,6 @@ type = "quarto" entrypoint = "report.qmd" title = "Regional Quarterly Sales Report" description = "This is the quarterly sales report, broken down by region." -thumbnail = "images/thumbnail.jpg" -tags = [ "sales", "quarterly", "regional" ] - -secrets = ["API_KEY"] [python] version = "3.11.3" @@ -24,22 +20,6 @@ version = "1.4" [environment] API_URL = "https://example.com/api" -[[schedules]] -start = "2023-10-25T08:00:00Z" -recurrence = "FREQ=MONTHLY;INTERVAL=3" - -[[access.users]] -id = "jqpublic" -guid = "536b456e-0311-4f92-ba10-dbf1db8a468e" -name = "John Q. Public" -permissions = "editor" - -[[access.groups]] -id = "Data Science Team" -guid = "8b4fde3e-f995-4894-bc02-ae47538262ff" -name = "Data Science Team" -permissions = "editor" - [connect.access] run-as = "rstudio-connect" run-as-current-user = false diff --git a/schema/draft/deploy.toml b/schema/draft/deploy.toml new file mode 100644 index 000000000..a125a1612 --- /dev/null +++ b/schema/draft/deploy.toml @@ -0,0 +1,67 @@ +"$schema" = "./posit-publishing-schema-v3.json" +type = "quarto" +entrypoint = "report.qmd" +title = "Regional Quarterly Sales Report" +description = "This is the quarterly sales report, broken down by region." +thumbnail = "images/thumbnail.jpg" +tags = [ "sales", "quarterly", "regional" ] + +secrets = ["API_KEY"] + +[python] +version = "3.11.3" +package-file = "requirements.txt" +package-manager = "pip" + +[r] +version = "4.3.1" +package-file = "renv.lock" +package-manager = "renv" + +[quarto] +version = "1.4" + +[environment] +API_URL = "https://example.com/api" + +[[schedules]] +start = "2023-10-25T08:00:00Z" +recurrence = "FREQ=MONTHLY;INTERVAL=3" + +[[access.users]] +id = "jqpublic" +guid = "536b456e-0311-4f92-ba10-dbf1db8a468e" +name = "John Q. Public" +permissions = "editor" + +[[access.groups]] +id = "Data Science Team" +guid = "8b4fde3e-f995-4894-bc02-ae47538262ff" +name = "Data Science Team" +permissions = "editor" + +[connect.access] +run-as = "rstudio-connect" +run-as-current-user = false + +[connect.runtime] +connection-timeout = 5 +read-timeout = 30 +init-timeout = 60 +idle-timeout = 120 +max-processes = 5 +min-processes = 1 +max-connections = 50 +load-factor = 0.5 + +[connect.kubernetes] +amd-gpu-limit = 0 +cpu-limit = 1 +cpu-request = 0.5 +image-name = "posit/connect-runtime-python3.11-r4.3" +memory-limit = "100000000" +memory-request = "20000000" +nvidia-gpu-limit = 0 +service-account-name = "posit-connect-content" +r-environment-management = true +py-environment-management = true diff --git a/schema/draft/posit-publishing-record-schema-v3.json b/schema/draft/posit-publishing-record-schema-v3.json new file mode 100644 index 000000000..3701e9f8c --- /dev/null +++ b/schema/draft/posit-publishing-record-schema-v3.json @@ -0,0 +1,65 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/rstudio/publishing-client/posit-publishing-record-schema-v3.json", + "type": "object", + "additionalProperties": false, + "description": "Posit Publishing Record", + "required": [ + "$schema", + "server-url", + "id", + "configuration-name" + ], + "properties": { + "$schema": { + "type": "string", + "format": "url", + "description": "URL of the json-schema definition for this file. Must be 'https://cdn.posit.co/connect/posit-publishing-record-schema-v3.json'.", + "enum": [ + "./posit-publishing-record-schema-v3.json" + ], + "examples": [ + "https://cdn.posit.co/connect/posit-publishing-record-schema-v3.json" + ] + }, + "server-url": { + "type": "string", + "format": "uri", + "description": "URL of the server where this content was deployed.", + "examples": [ + "https://connect.example.com" + ] + }, + "id": { + "type": "string", + "description": "Unique ID of this deployment.", + "examples": [ + "de2e7bdb-b085-401e-a65c-443e40009749" + ] + }, + "configuration-name": { + "type": "string", + "description": "Name of the configuration that was used during deployment.", + "examples": [ + "production" + ] + }, + "configuration": { + "$ref": "./posit-publishing-schema-v3.json" + }, + "files": { + "type": "array", + "items": { + "type": [ + "string" + ] + }, + "description": "Project-relative paths of the files that were included in the deployment.", + "examples": [ + "app.py", + "model/weights.csv" + ] + + } + } +} diff --git a/schema/draft/posit-publishing-schema-v3.json b/schema/draft/posit-publishing-schema-v3.json new file mode 100644 index 000000000..faa3b29c0 --- /dev/null +++ b/schema/draft/posit-publishing-schema-v3.json @@ -0,0 +1,531 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/rstudio/publishing-client/posit-publishing-schema-v3.json", + "type": "object", + "additionalProperties": false, + "description": "Posit Publishing Configuration", + "required": [ + "$schema", + "type", + "entrypoint" + ], + "properties": { + "$schema": { + "type": "string", + "format": "url", + "description": "URL of the json-schema definition for this file. Must be 'https://cdn.posit.co/connect/posit-publishing-schema-v3.json'.", + "enum": [ + "./posit-publishing-schema-v3.json" + ], + "examples": [ + "https://cdn.posit.co/connect/posit-publishing-schema-v3.json" + ] + }, + "type": { + "type": "string", + "description": "Indicates the type of content being deployed.", + "enum": [ + "html", + "jupyter-notebook", + "jupyter-voila", + "python-bokeh", + "python-dash", + "python-fastapi", + "python-flask", + "python-shiny", + "python-streamlit", + "quarto-shiny", + "quarto", + "r-plumber", + "r-shiny", + "rmd-shiny", + "rmd" + ], + "examples": [ + "quarto" + ] + }, + "entrypoint": { + "type": "string", + "description": "Name of the primary file containing the content. For some types of Python executable content, this may also indicate the object within the file in module:object format.", + "examples": [ + "app.py", + "report.qmd" + ] + }, + "title": { + "type": "string", + "pattern": "^[^\t\n\f\r]{3,1024}$|", + "description": "Human-readable title for this content.", + "examples": [ + "Quarterly Sales Report" + ] + }, + "description": { + "type": "string", + "description": "Human-readable description for this content.", + "examples": [ + "This is the quarterly sales report, broken down by region." + ] + }, + "thumbnail": { + "type": "string", + "description": "Path to thumbnail preview image for this content.", + "examples": [ + "images/thumbnail.jpg" + ] + }, + "tags": { + "type": "array", + "description": "List of tags to apply to this deployment. When publishing to Connect, tags must be pre-defined by an administrator.", + "items": { + "type": "string", + "examples": [ + "sales", + "quarterly", + "regional" + ] + } + }, + "python": { + "type": "object", + "additionalProperties": false, + "description": "Python language and dependencies.", + "required": [ + "version" + ], + "properties": { + "version": { + "type": "string", + "description": "Python version. The server must have a matching Python version in order to run the content.", + "examples": [ + "3.11.3", + "3.11" + ] + }, + "package-file": { + "type": "string", + "description": "File containing package dependencies. The file must exist and be listed under 'files'.", + "default": "requirements.txt", + "examples": [ + "requirements.txt" + ] + }, + "package-manager": { + "type": "string", + "default": "pip", + "enum": [ + "pip", + "conda", + "pipenv", + "poetry", + "none" + ], + "description": "Package manager that will install the dependencies. If package-manager is none, dependencies will not be installed.", + "examples": [ + "pip" + ] + } + } + }, + "r": { + "type": "object", + "additionalProperties": false, + "description": "R language and dependencies.", + "required": [ + "version" + ], + "properties": { + "version": { + "type": "string", + "description": "R version. The server must have a similar R version in order to run the content.", + "examples": [ + "4.3.1" + ] + }, + "package-file": { + "type": "string", + "default": "renv.lock", + "description": "File containing package dependencies. The file must exist and be listed under 'files'.", + "examples": [ + "renv.lock" + ] + }, + "package-manager": { + "type": "string", + "default": "renv", + "enum": [ + "renv", + "none" + ], + "description": "Package manager that will install the dependencies. If package-manager is none, dependencies will not be installed.", + "examples": [ + "renv" + ] + } + } + }, + "quarto": { + "type": "object", + "additionalProperties": false, + "description": "Quarto version required to run the content.", + "required": [ + "version" + ], + "properties": { + "version": { + "type": "string", + "description": "Quarto version. The server must have a similar Quarto version in order to run the content.", + "examples": [ + "1.4" + ] + }, + "engines": { + "type": "array", + "description": "List of Quarto engines required for this content.", + "items": { + "type": "string", + "enum": [ + "knitr", + "jupyter" + ], + "examples": [ + "knitr", + "jupyter" + ] + } + } + } + }, + "environment": { + "type": "object", + "additionalProperties": { + "type": [ + "string" + ] + }, + "description": "Environment variable/value map. Secrets such as API keys or tokens should not be stored here.", + "examples": [ + { + "API_URL": "https://example.com/api" + } + ] + }, + "secrets": { + "type": "array", + "items": { + "type": [ + "string" + ] + }, + "description": "Names of secrets that should be injected as environment variables.", + "examples": [ + "API_KEY" + ] + }, + "schedules": { + "type": "array", + "description": "Schedules for recurring execution of this content. Only applies to reports, such as Quarto, R Markdown, and Jupyter Notebooks.", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "start", + "recurrence" + ], + "properties": { + "start": { + "type": "string", + "description": "Time for the first run of the content.", + "examples": [ + "2023-10-25T08:00:00Z" + ] + }, + "recurrence": { + "type": "string", + "description": "Recurrence scheme for the content, in cron or iCalendar RRULE format.", + "examples": [ + "FREQ=MONTHLY;INTERVAL=3", + "0 2 * * *" + ] + } + } + } + }, + "access": { + "type": "object", + "additionalProperties": false, + "description": "Access control settings.", + "properties": { + "type": { + "type": "string", + "default": "acl", + "description": "Type of access control. 'public' make the deployment public (no login required), with no login required. 'all-users' allows all logged-in users to access the content. 'acl' (the default) allows only specific users and groups to access the content.", + "enum": [ + "public", + "all-users", + "acl" + ], + "examples": [ + "all-users" + ] + }, + "users": { + "type": "array", + "description": "List of users who have access to this content.", + "items": { + "type": "object", + "additionalProperties": false, + "anyOf": [ + { "required": ["id"] }, + { "required": ["guid"] } + ], + "properties": { + "id": { + "type": "string", + "description": "Unique ID from the authentication provider that identifies this user. This may be a username, LDAP CN/DN, email address, etc. depending on the provider.", + "examples": [ + "jqpublic" + ] + }, + "guid": { + "type": "string", + "description": "Unique identifier assigned by Connect. When deploying to the same server, this will determine the user who is granted access. When deploying to a different server, the provider-id will be used to look up the user.", + "examples": [ + "cd2e7cef-a195-512e-b76d-554f4f5a239c" + ] + }, + "name": { + "type": "string", + "description": "User's name. This field is informational only.", + "examples": [ + "John Q. Public" + ] + }, + "permissions": { + "type": "string", + "description": "Permission level assigned to this user.", + "enum": ["viewer", "editor", "owner"], + "examples": [ + "viewer" + ] + } + } + } + }, + "groups": { + "type": "array", + "description": "List of groups who have access to this content.", + "items": { + "type": "object", + "additionalProperties": false, + "anyOf": [ + { "required": ["id"] }, + { "required": ["guid"] } + ], + "properties": { + "id": { + "type": "string", + "description": "Unique ID from the authentication provider that identifies this group. For groups created by Connect, this will be the group name. This may be a group name, LDAP CN/DN, etc. depending on the provider.", + "examples": [ + "Data Science Team" + ] + }, + "guid": { + "type": "string", + "description": "Unique identifier assigned by Connect. When deploying to the same server, this will determine the user who is granted access. When deploying to a different server, the provider-id will be used to look up the user.", + "examples": [ + "8b4fde3e-f995-4894-bc02-ae47538262ff" + ] + }, + "name": { + "type": "string", + "description": "Group name. This field is informational only.", + "examples": [ + "Data Science Team" + ] + }, + "permissions": { + "type": "string", + "default": "viewer", + "description": "Permission level assigned to this group.", + "enum": ["viewer", "editor", "owner"], + "examples": [ + "editor" + ] + } + } + } + } + } + }, + "connect": { + "type": "object", + "additionalProperties": false, + "description": "Setting specific to Posit Connect deployments.", + "properties": { + "access": { + "run-as": { + "type": "string", + "description": "The system username under which the content should be run. Must be an existing user in the allowed group. You must be an administrator to set this value.", + "examples": [ + "rstudio-connect" + ] + }, + "run-as-current-user": { + "type": "boolean", + "default": false, + "description": "For application content types, run a separate process for each visiting user under that user's server account. Requires PAM authentication on the Posit Connect server. You must be an administrator to set this value." + } + }, + "runtime": { + "type": "object", + "additionalProperties": false, + "description": "Runtime settings for application content types.", + "properties": { + "connection-timeout": { + "type": "integer", + "minimum": 0, + "description": "Maximum number of seconds allowed without data sent or received across a client connection. A value of `0` means connections will never time-out (not recommended).", + "examples": [ + 5 + ] + }, + "read-timeout": { + "type": "integer", + "minimum": 0, + "description": "Maximum number of seconds allowed without data received from a client connection. A value of `0` means a lack of client (browser) interaction never causes the connection to close.", + "examples": [ + 30 + ] + }, + "init-timeout": { + "type": "integer", + "description": "The maximum number of seconds allowed for an interactive application to start. Posit Connect must be able to connect to a newly launched application before this threshold has elapsed.", + "examples": [ + 60 + ] + }, + "idle-timeout": { + "type": "integer", + "description": "The maximum number of seconds a worker process for an interactive application to remain alive after it goes idle (no active connections).", + "examples": [ + 120 + ] + }, + "max-processes": { + "type": "integer", + "minimum": 1, + "description": "Specifies the total number of concurrent processes allowed for a single interactive application.", + "examples": [ + 5 + ] + }, + "min-processes": { + "type": "integer", + "minimum": 0, + "description": "Specifies the minimum number of concurrent processes allowed for a single interactive application.", + "examples": [ + 1 + ] + }, + "max-connections": { + "type": "integer", + "minimum": 1, + "description": "Specifies the maximum number of client connections allowed to an individual process. Incoming connections which will exceed this limit are routed to a new process or rejected.", + "examples": [ + 50 + ] + }, + "load-factor": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Controls how aggressively new processes are spawned. The valid range is between 0.0 and 1.0.", + "examples": [ + 0.5 + ] + } + } + }, + "kubernetes": { + "type": "object", + "additionalProperties": false, + "description": "Settings used with Posit Connect's off-host execution feature, where content is run in Kubernetes.", + "properties": { + "amd-gpu-limit": { + "type": "integer", + "description": "The number of AMD GPUs that will be allocated by Kubernetes to run this content.", + "examples": [ + 0 + ] + }, + "cpu-limit": { + "type": "integer", + "description": "The maximum amount of compute power this content will be allowed to consume when executing or rendering, expressed in CPU Units, where 1.0 unit is equivalent to 1 physical or virtual core. Fractional values are allowed. If the process tries to use more CPU than allowed, it will be throttled.", + "examples": [ + 1 + ] + }, + "cpu-request": { + "type": "number", + "description": "The minimum amount of compute power this content needs when executing virtual core. Fractional values are allowed.", + "examples": [ + 0.5 + ] + }, + "image-name": { + "type": "string", + "description": "Name of the target container image.", + "examples": [ + "posit/connect-runtime-python3.11-r4.3" + ] + }, + "memory-limit": { + "type": "string", + "description": "The maximum amount of RAM this content will be allowed to consume when executing or rendering, expressed in bytes. If the process tries to use more memory than allowed, it will be terminated", + "examples": [ + "100000000" + ] + }, + "memory-request": { + "type": "string", + "description": "The minimum amount of RAM this content needs when executing or rendering, expressed in bytes.", + "examples": [ + "20000000" + ] + }, + "nvidia-gpu-limit": { + "type": "integer", + "description": "The number of NVIDIA GPUs that will be allocated by Kubernetes to run this content.", + "examples": [ + 0 + ] + }, + "service-account-name": { + "type": "string", + "description": "The name of the Kubernetes service account that is used to run this content. It must adhere to Kubernetes service account naming rules. You must be an administrator to set this value.", + "examples": [ + "posit-connect-content" + ] + }, + "r-environment-management": { + "type": "boolean", + "description": "Enables or disables R environment management. When false, Posit Connect will not install R packages and instead assume that all required packages are present in the container image.", + "examples": [ + true + ] + }, + "py-environment-management": { + "type": "boolean", + "description": "Enables or disables Python environment management. When false, Posit Connect will not install Python packages and instead assume that all required packages are present in the container image.", + "examples": [ + true + ] + } + } + } + } + } + } +} diff --git a/schema/draft/record.toml b/schema/draft/record.toml new file mode 100644 index 000000000..aad3d9ad3 --- /dev/null +++ b/schema/draft/record.toml @@ -0,0 +1,72 @@ +"$schema" = "./posit-publishing-record-schema-v3.json" +server-url = "https://connect.example.com" +id = "de2e7bdb-b085-401e-a65c-443e40009749" +configuration-name = "production.json" + +[configuration] +"$schema" = "./posit-publishing-schema-v3.json" +type = "quarto" +entrypoint = "report.qmd" +title = "Regional Quarterly Sales Report" +description = "This is the quarterly sales report, broken down by region." +thumbnail = "images/thumbnail.jpg" +tags = [ "sales", "quarterly", "regional" ] +secrets = ["API_KEY"] + +[configuration.python] +version = "3.11.3" +package_file = "requirements.txt" +package_manager = "pip" + +[configuration.r] +version = "4.3.1" +package_file = "renv.lock" +package_manager = "renv" + +[configuration.quarto] +version = "1.4" + +[configuration.environment] +API_URL = "https://example.com/api" + +[[configuration.schedules]] +start = "2023-10-25T08:00:00Z" +recurrence = "FREQ=MONTHLY;INTERVAL=3" + +[[configuration.access.users]] +id = "jqpublic" +guid = "536b456e-0311-4f92-ba10-dbf1db8a468e" +name = "John Q. Public" +permissions = "editor" + +[[configuration.access.groups]] +id = "Data Science Team" +guid = "8b4fde3e-f995-4894-bc02-ae47538262ff" +name = "Data Science Team" +permissions = "editor" + +[configuration.connect.access] +run_as = "rstudio-connect" +run_as_current_user = false + +[configuration.connect.runtime] +connection_timeout = 5 +read_timeout = 30 +init_timeout = 60 +idle_timeout = 120 +max_processes = 5 +min_processes = 1 +max_conns_per_process = 50 +load_factor = 0.5 + +[configuration.connect.kubernetes] +amd_gpu_limit = 0 +cpu_limit = 1 +cpu_request = 0.5 +image_name = "posit/connect-runtime-python3.11-r4.3" +memory_limit = "100000000" +memory_request = "20000000" +nvidia_gpu_limit = 0 +service_account_name = "posit-connect-content" +r_environment_management = true +py_environment_management = true diff --git a/schema/posit-publishing-schema-v3.json b/schema/posit-publishing-schema-v3.json index faa3b29c0..9b883e198 100644 --- a/schema/posit-publishing-schema-v3.json +++ b/schema/posit-publishing-schema-v3.json @@ -68,25 +68,6 @@ "This is the quarterly sales report, broken down by region." ] }, - "thumbnail": { - "type": "string", - "description": "Path to thumbnail preview image for this content.", - "examples": [ - "images/thumbnail.jpg" - ] - }, - "tags": { - "type": "array", - "description": "List of tags to apply to this deployment. When publishing to Connect, tags must be pre-defined by an administrator.", - "items": { - "type": "string", - "examples": [ - "sales", - "quarterly", - "regional" - ] - } - }, "python": { "type": "object", "additionalProperties": false, @@ -211,154 +192,6 @@ } ] }, - "secrets": { - "type": "array", - "items": { - "type": [ - "string" - ] - }, - "description": "Names of secrets that should be injected as environment variables.", - "examples": [ - "API_KEY" - ] - }, - "schedules": { - "type": "array", - "description": "Schedules for recurring execution of this content. Only applies to reports, such as Quarto, R Markdown, and Jupyter Notebooks.", - "items": { - "type": "object", - "additionalProperties": false, - "required": [ - "start", - "recurrence" - ], - "properties": { - "start": { - "type": "string", - "description": "Time for the first run of the content.", - "examples": [ - "2023-10-25T08:00:00Z" - ] - }, - "recurrence": { - "type": "string", - "description": "Recurrence scheme for the content, in cron or iCalendar RRULE format.", - "examples": [ - "FREQ=MONTHLY;INTERVAL=3", - "0 2 * * *" - ] - } - } - } - }, - "access": { - "type": "object", - "additionalProperties": false, - "description": "Access control settings.", - "properties": { - "type": { - "type": "string", - "default": "acl", - "description": "Type of access control. 'public' make the deployment public (no login required), with no login required. 'all-users' allows all logged-in users to access the content. 'acl' (the default) allows only specific users and groups to access the content.", - "enum": [ - "public", - "all-users", - "acl" - ], - "examples": [ - "all-users" - ] - }, - "users": { - "type": "array", - "description": "List of users who have access to this content.", - "items": { - "type": "object", - "additionalProperties": false, - "anyOf": [ - { "required": ["id"] }, - { "required": ["guid"] } - ], - "properties": { - "id": { - "type": "string", - "description": "Unique ID from the authentication provider that identifies this user. This may be a username, LDAP CN/DN, email address, etc. depending on the provider.", - "examples": [ - "jqpublic" - ] - }, - "guid": { - "type": "string", - "description": "Unique identifier assigned by Connect. When deploying to the same server, this will determine the user who is granted access. When deploying to a different server, the provider-id will be used to look up the user.", - "examples": [ - "cd2e7cef-a195-512e-b76d-554f4f5a239c" - ] - }, - "name": { - "type": "string", - "description": "User's name. This field is informational only.", - "examples": [ - "John Q. Public" - ] - }, - "permissions": { - "type": "string", - "description": "Permission level assigned to this user.", - "enum": ["viewer", "editor", "owner"], - "examples": [ - "viewer" - ] - } - } - } - }, - "groups": { - "type": "array", - "description": "List of groups who have access to this content.", - "items": { - "type": "object", - "additionalProperties": false, - "anyOf": [ - { "required": ["id"] }, - { "required": ["guid"] } - ], - "properties": { - "id": { - "type": "string", - "description": "Unique ID from the authentication provider that identifies this group. For groups created by Connect, this will be the group name. This may be a group name, LDAP CN/DN, etc. depending on the provider.", - "examples": [ - "Data Science Team" - ] - }, - "guid": { - "type": "string", - "description": "Unique identifier assigned by Connect. When deploying to the same server, this will determine the user who is granted access. When deploying to a different server, the provider-id will be used to look up the user.", - "examples": [ - "8b4fde3e-f995-4894-bc02-ae47538262ff" - ] - }, - "name": { - "type": "string", - "description": "Group name. This field is informational only.", - "examples": [ - "Data Science Team" - ] - }, - "permissions": { - "type": "string", - "default": "viewer", - "description": "Permission level assigned to this group.", - "enum": ["viewer", "editor", "owner"], - "examples": [ - "editor" - ] - } - } - } - } - } - }, "connect": { "type": "object", "additionalProperties": false, diff --git a/schema/record.toml b/schema/record.toml index 802dd74ea..1de26aeac 100644 --- a/schema/record.toml +++ b/schema/record.toml @@ -1,7 +1,7 @@ "$schema" = "./posit-publishing-record-schema-v3.json" server-url = "https://connect.example.com" id = "de2e7bdb-b085-401e-a65c-443e40009749" -configuration-file = "production.json" +configuration-name = "production" [configuration] "$schema" = "./posit-publishing-schema-v3.json" @@ -9,42 +9,20 @@ type = "quarto" entrypoint = "report.qmd" title = "Regional Quarterly Sales Report" description = "This is the quarterly sales report, broken down by region." -thumbnail = "images/thumbnail.jpg" -tags = [ "sales", "quarterly", "regional" ] -secrets = ["API_KEY"] -[configuration.dependencies.python] +[configuration.python] version = "3.11.3" package_file = "requirements.txt" package_manager = "pip" -[configuration.dependencies.r] +[configuration.r] version = "4.3.1" package_file = "renv.lock" package_manager = "renv" -[configuration.dependencies.quarto] +[configuration.quarto] version = "1.4" -[configuration.environment] -API_URL = "https://example.com/api" - -[[configuration.schedules]] -start = "2023-10-25T08:00:00Z" -recurrence = "FREQ=MONTHLY;INTERVAL=3" - -[[configuration.access.users]] -id = "jqpublic" -guid = "536b456e-0311-4f92-ba10-dbf1db8a468e" -name = "John Q. Public" -permissions = "editor" - -[[configuration.access.groups]] -id = "Data Science Team" -guid = "8b4fde3e-f995-4894-bc02-ae47538262ff" -name = "Data Science Team" -permissions = "editor" - [configuration.connect.access] run_as = "rstudio-connect" run_as_current_user = false