Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cloud static outputs #825

Merged
merged 37 commits into from
May 9, 2023
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
e8a9364
initial implementation for static outputs in cloud
mslynch Mar 21, 2023
0b3096d
save content id for cloud applications in dcf file's applicationId
mslynch Mar 22, 2023
5a931d4
better json-ing
mslynch Mar 22, 2023
871519e
make deploys and redeploys work for static applications
mslynch Mar 27, 2023
31ccaed
remove bad argument
mslynch Mar 27, 2023
4b32d80
revert change to give deploymentTargetForApp appId a default argument
mslynch Mar 27, 2023
51daf95
Merge branch 'main' into cloud-static-outputs
mslynch Mar 27, 2023
1675271
remove extraneous character in logging
mslynch Mar 27, 2023
42cd94a
save appId differently for cloud vs connect
mslynch Mar 31, 2023
c560e9f
Add unit tests of cloud functionality (#3)
mbaynton Apr 20, 2023
e764dbc
If output is in trashed or archived state, set it to active
omar-rs Apr 26, 2023
ef39cc0
Merge pull request #5 from mslynch/handle-trashed-archived
omar-rs Apr 27, 2023
629a6bd
version dcf files to distinguish between cloud application and conten…
mslynch Apr 28, 2023
ee06339
Merge branch 'main' into cloud-static-outputs
mslynch Apr 28, 2023
67d8221
Merge pull request #823 from mslynch/cloud-static-outputs
mslynch Apr 28, 2023
3959197
appease linter
mslynch Apr 28, 2023
3a31ee5
rename cloud client getApplication id parameter and clarify dcf versi…
mslynch Apr 28, 2023
2c6ec3f
name arguments in http.R
mslynch May 1, 2023
23f7018
check solely for dcfVersion being NA
mslynch May 1, 2023
bb81aa3
use %||% instead of ifelse to determine output_id
mslynch May 1, 2023
7e068b4
refactor revision creation to be closer to application creation
mslynch May 1, 2023
c995cad
use regular if/else to determine application_type
mslynch May 1, 2023
ac360e6
in cloud client createApplication/getApplication, return content id a…
mslynch May 1, 2023
61abe5b
always use application$id in saveDeployment
mslynch May 1, 2023
30b5d5f
update client-cloud.R getApplication with note about when to consider…
mslynch May 1, 2023
de5ad91
use paste0 instead of paste in cloud-client.R
mslynch May 1, 2023
730a30c
set default value for dcfFile argument
mslynch May 2, 2023
e01eb2a
add news bullets about DCF version, Cloud appId, and Cloud static con…
mslynch May 2, 2023
215fcbb
Merge branch 'main' into cloud-static-outputs
mslynch May 2, 2023
ab98e61
state that deployApp also supports cloud static content in news bullet
mslynch May 2, 2023
8b817aa
Merge branch 'main' into cloud-static-outputs
mslynch May 2, 2023
0ceb441
Merge branch 'main' into cloud-static-outputs
mslynch May 2, 2023
4fc6ee9
correct variable name on comment explaining outputOrApplicationId
mslynch May 3, 2023
8c7e5af
Merge branch 'main' into cloud-static-outputs
mslynch May 8, 2023
dfe3284
make news bullet about appId change more comprehensible to non-Posit …
mslynch May 8, 2023
75b39cd
update TODO with correct version of next release
mslynch May 8, 2023
a43176b
Merge branch 'main' into cloud-static-outputs
mslynch May 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# rsconnect (development version)

* `deployDoc()` and `deployApp()` now support deploying static content to Posit
Cloud.

* For cloud deployments, appId now represents the content id (as seen in URLs
mslynch marked this conversation as resolved.
Show resolved Hide resolved
of the format `https://posit.cloud/content/{id}`) instead of the application
id.

* A `version` field has been added to deployment DCF files to facilitate file
format changes. Its value for this release is `1`.

* `deployApp()`'s `quarto` argument now takes values `TRUE`, `FALSE` or
`NA`. The previous value (a path to a quarto binary) is now deprecated,
and instead we automatically figure out the packge from `QUARTO_PATH` and
Expand Down
2 changes: 1 addition & 1 deletion R/applications.R
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ getApplication <- function(account, server, appId) {
client <- clientForAccount(accountDetails)

withCallingHandlers(
client$getApplication(appId),
client$getApplication(appId, deploymentRecordVersion),
rsconnect_http_404 = function(err) {
cli::cli_abort("Can't find app with id {.str {appId}}", parent = err)
}
Expand Down
116 changes: 73 additions & 43 deletions R/client-cloud.R
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ cloudClient <- function(service, authInfo) {

getAccountUsage = function(accountId, usageType = "hours", applicationId = NULL,
from = NULL, until = NULL, interval = NULL) {
path <- paste("/accounts/", accountId, "/usage/", usageType, "/", sep = "")
path <- paste0("/accounts/", accountId, "/usage/", usageType, "/")
query <- list()
if (!is.null(applicationId))
query$application <- applicationId
Expand All @@ -37,12 +37,12 @@ cloudClient <- function(service, authInfo) {
},

getBundle = function(bundleId) {
path <- paste("/bundles/", bundleId, sep = "")
path <- paste0("/bundles/", bundleId)
GET(service, authInfo, path)
},

updateBundleStatus = function(bundleId, status) {
path <- paste("/bundles/", bundleId, "/status", sep = "")
path <- paste0("/bundles/", bundleId, "/status")
json <- list()
json$status <- status
POST_JSON(service, authInfo, path, json)
Expand All @@ -66,20 +66,46 @@ cloudClient <- function(service, authInfo) {
listRequest(service, authInfo, path, query, "applications")
},

getApplication = function(applicationId) {
path <- paste("/applications/", applicationId, sep = "")
application <- GET(service, authInfo, path)
getApplication = function(outputOrApplicationId, deploymentRecordVersion) {
if (is.na(deploymentRecordVersion)) {
# In pre-versioned dcf files, contentOrAppId is the id of the application.
mslynch marked this conversation as resolved.
Show resolved Hide resolved
# TODO: consider removing support for this case a year after the release of 0.8.29
path <- paste0("/applications/", outputOrApplicationId)
application <- GET(service, authInfo, path)

output_id <- application$output_id %||% application$content_id

path <- paste0("/outputs/", output_id)
output <- GET(service, authInfo, path)
} else {
# from dcf version >= 1, outputOrApplicationId is the id of the output.
path <- paste0("/outputs/", outputOrApplicationId)
output <- GET(service, authInfo, path)

path <- paste0("/applications/", output$source_id)
application <- GET(service, authInfo, path)
application
}

output_id <- application$content_id
path <- paste("/content/", output_id, sep = "")
# if the output is trashed or archived, restore it to the active state
if (output$state == "trashed" || output$state == "archived") {
json <- list()
json$state <- "active"
PATCH_JSON(service, authInfo, paste0("/outputs/", output$id), json)
}

applications_output <- GET(service, authInfo, path)
application$url <- applications_output$url
# Each redeployment of a static output creates a new application. Since
# those applications can be deleted, it's more reliable to reference
# outputs by their own id instead of the applications'.
application$application_id <- application$id
application$id <- output$id
application$url <- output$url
application$name <- output$name
application
},

getApplicationMetrics = function(applicationId, series, metrics, from = NULL, until = NULL, interval = NULL) {
path <- paste("/applications/", applicationId, "/metrics/", series, "/", sep = "")
path <- paste0("/applications/", applicationId, "/metrics/", series, "/")
query <- list()
m <- paste(lapply(metrics, function(x) { paste("metric", urlEncode(x), sep = "=") }), collapse = "&")
if (!is.null(from))
Expand All @@ -97,59 +123,61 @@ cloudClient <- function(service, authInfo) {
GET(service, authInfo, path, query)
},

createApplication = function(name, title, template, accountId) {
createApplication = function(name, title, template, accountId, appMode) {
json <- list()
json$name <- name
json$application_type <- if (appMode == "static") "static" else "connect"

currentApplicationId <- Sys.getenv("LUCID_APPLICATION_ID")
if (currentApplicationId != "") {
print("Found application...")
path <- paste("/applications/", currentApplicationId, sep = "")
path <- paste0("/applications/", currentApplicationId)
current_application <- GET(service, authInfo, path)
project_id <- current_application$content_id

# in case the source cloud project is a temporary copy, there is no
# content id. The output will be published without a space id.
if (!is.null(project_id)) {
path <- paste("/content/", project_id, sep = "")
path <- paste0("/content/", project_id)
current_project <- GET(service, authInfo, path)
json$project <- current_project$id
json$space <- current_project$space_id
}
}
output <- POST_JSON(service, authInfo, "/outputs", json)
path <- paste("/applications/", output$source_id, sep = "")
path <- paste0("/applications/", output$source_id)
application <- GET(service, authInfo, path)
application$application_id <- application$id
application$id <- output$id
# this swaps the "application url" for the "content url". So we end up redirecting to the right spot after deployment.
application$url <- output$url
application
},

listApplicationProperties = function(applicationId) {
path <- paste("/applications/", applicationId, "/properties/", sep = "")
path <- paste0("/applications/", applicationId, "/properties/")
GET(service, authInfo, path)
},

setApplicationProperty = function(applicationId, propertyName,
propertyValue, force = FALSE) {
path <- paste("/applications/", applicationId, "/properties/",
propertyName, sep = "")
path <- paste0("/applications/", applicationId, "/properties/",
propertyName)
v <- list()
v$value <- propertyValue
query <- paste("force=", if (force) "1" else "0", sep = "")
query <- paste0("force=", if (force) "1" else "0")
PUT_JSON(service, authInfo, path, v, query)
},

unsetApplicationProperty = function(applicationId, propertyName,
force = FALSE) {
path <- paste("/applications/", applicationId, "/properties/",
propertyName, sep = "")
query <- paste("force=", if (force) "1" else "0", sep = "")
path <- paste0("/applications/", applicationId, "/properties/",
propertyName)
query <- paste0("force=", if (force) "1" else "0")
DELETE(service, authInfo, path, query)
},

uploadApplication = function(applicationId, bundlePath) {
path <- paste("/applications/", applicationId, "/upload", sep = "")
path <- paste0("/applications/", applicationId, "/upload")
POST(
service,
authInfo,
Expand All @@ -159,8 +187,14 @@ cloudClient <- function(service, authInfo) {
)
},

deployApplication = function(applicationId, bundleId = NULL) {
path <- paste("/applications/", applicationId, "/deploy", sep = "")
createRevision = function(application) {
path <- paste0("/outputs/", application$id, "/revisions")
revision <- POST_JSON(service, authInfo, path, data.frame())
revision$application_id
},

deployApplication = function(application, bundleId = NULL) {
path <- paste0("/applications/", application$application_id, "/deploy")
json <- list()
if (length(bundleId) > 0 && nzchar(bundleId))
json$bundle <- as.numeric(bundleId)
Expand All @@ -170,19 +204,18 @@ cloudClient <- function(service, authInfo) {
},

terminateApplication = function(applicationId) {
path <- paste("/applications/", applicationId, "/terminate", sep = "")
path <- paste0("/applications/", applicationId, "/terminate")
POST(service, authInfo, path)
},

purgeApplication = function(applicationId) {
path <- paste("/applications/", applicationId, "/purge", sep = "")
path <- paste0("/applications/", applicationId, "/purge")
POST(service, authInfo, path)
},

inviteApplicationUser = function(applicationId, email,
invite_email = NULL, invite_email_message = NULL) {
path <- paste("/applications/", applicationId, "/authorization/users",
sep = "")
path <- paste0("/applications/", applicationId, "/authorization/users")
json <- list()
json$email <- email
if (!is.null(invite_email))
Expand All @@ -193,32 +226,29 @@ cloudClient <- function(service, authInfo) {
},

addApplicationUser = function(applicationId, userId) {
path <- paste("/applications/", applicationId, "/authorization/users/",
userId, sep = "")
path <- paste0("/applications/", applicationId, "/authorization/users/",
userId)
PUT(service, authInfo, path, NULL)
},

removeApplicationUser = function(applicationId, userId) {
path <- paste("/applications/", applicationId, "/authorization/users/",
userId, sep = "")
path <- paste0("/applications/", applicationId, "/authorization/users/",
userId)
DELETE(service, authInfo, path)
},

listApplicationAuthorization = function(applicationId) {
path <- paste("/applications/", applicationId, "/authorization",
sep = "")
path <- paste0("/applications/", applicationId, "/authorization")
listRequest(service, authInfo, path, NULL, "authorization")
},

listApplicationUsers = function(applicationId) {
path <- paste("/applications/", applicationId, "/authorization/users",
sep = "")
path <- paste0("/applications/", applicationId, "/authorization/users")
listRequest(service, authInfo, path, NULL, "users")
},

listApplicationGroups = function(applicationId) {
path <- paste("/applications/", applicationId, "/authorization/groups",
sep = "")
path <- paste0("/applications/", applicationId, "/authorization/groups")
listRequest(service, authInfo, path, NULL, "groups")
},

Expand All @@ -239,12 +269,12 @@ cloudClient <- function(service, authInfo) {
},

getTaskInfo = function(taskId) {
path <- paste("/tasks/", taskId, sep = "")
path <- paste0("/tasks/", taskId)
GET(service, authInfo, path)
},

getTaskLogs = function(taskId) {
path <- paste("/tasks/", taskId, "/logs/", sep = "")
path <- paste0("/tasks/", taskId, "/logs/")
GET(service, authInfo, path)
},

Expand All @@ -254,7 +284,7 @@ cloudClient <- function(service, authInfo) {
cat("Waiting for task: ", taskId, "\n", sep = "")
}

path <- paste("/tasks/", taskId, sep = "")
path <- paste0("/tasks/", taskId)

lastStatus <- NULL
while (TRUE) {
Expand Down
8 changes: 4 additions & 4 deletions R/client-connect.R
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ connectClient <- function(service, authInfo) {
listRequest(service, authInfo, path, query, "applications")
},

createApplication = function(name, title, template, accountId) {
createApplication = function(name, title, template, accountId, appMode) {
# add name; inject title if specified
details <- list(name = name)
if (!is.null(title) && nzchar(title))
Expand All @@ -75,8 +75,8 @@ connectClient <- function(service, authInfo) {
)
},

deployApplication = function(applicationId, bundleId = NULL) {
path <- paste("/applications/", applicationId, "/deploy", sep = "")
deployApplication = function(application, bundleId = NULL) {
path <- paste("/applications/", application$id, "/deploy", sep = "")
json <- list()
json$bundle <- as.numeric(bundleId)
POST_JSON(service, authInfo, path, json)
Expand All @@ -87,7 +87,7 @@ connectClient <- function(service, authInfo) {
"/applications/", applicationId, "/config", sep = ""))
},

getApplication = function(applicationId) {
getApplication = function(applicationId, deploymentRecordVersion) {
GET(service, authInfo, paste0("/applications/", applicationId))
},

Expand Down
8 changes: 4 additions & 4 deletions R/client-shinyapps.R
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ shinyAppsClient <- function(service, authInfo) {
listRequest(service, authInfo, path, query, "applications")
},

getApplication = function(applicationId) {
getApplication = function(applicationId, deploymentRecordVersion) {
path <- paste("/applications/", applicationId, sep = "")
GET(service, authInfo, path)
},
Expand All @@ -88,7 +88,7 @@ shinyAppsClient <- function(service, authInfo) {
GET(service, authInfo, path, query)
},

createApplication = function(name, title, template, accountId) {
createApplication = function(name, title, template, accountId, appMode) {
json <- list()
json$name <- name
# the title field is only used on connect
Expand Down Expand Up @@ -131,8 +131,8 @@ shinyAppsClient <- function(service, authInfo) {
)
},

deployApplication = function(applicationId, bundleId = NULL) {
path <- paste("/applications/", applicationId, "/deploy", sep = "")
deployApplication = function(application, bundleId = NULL) {
path <- paste("/applications/", application$id, "/deploy", sep = "")
json <- list()
if (length(bundleId) > 0 && nzchar(bundleId))
json$bundle <- as.numeric(bundleId)
Expand Down
Loading