Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
used automatically. `rsconnect login` sets the server as default unless
`--no-set-default` is passed. `CONNECT_SERVER` still takes precedence.
- New `environment` subcommand for managing execution environments on Connect.
- New `integration` subcommand for managing OAuth integrations on Connect.

### Added

Expand Down
3 changes: 3 additions & 0 deletions docs/commands/integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
::: mkdocs-click
:module: rsconnect.main
:command: integration
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ nav:
- details: commands/details.md
- environment: commands/environment.md
- info: commands/info.md
- integration: commands/integration.md
- list: commands/list.md
- login: commands/login.md
- logout: commands/logout.md
Expand Down
118 changes: 118 additions & 0 deletions rsconnect/actions_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
"""
Public API for managing OAuth integrations on Posit Connect.
"""

from __future__ import annotations

from typing import Optional, Union

from .api import RSConnectClient, RSConnectServer, SPCSConnectServer
from .models import (
OAuthIntegration,
OAuthIntegrationInput,
OAuthIntegrationPermission,
OAuthIntegrationUpdate,
OAuthTemplate,
)


def list_oauth_integrations(
connect_server: Union[RSConnectServer, SPCSConnectServer],
) -> list[OAuthIntegration]:
with RSConnectClient(connect_server) as client:
return client.oauth_integration_list()


def get_oauth_integration(
connect_server: Union[RSConnectServer, SPCSConnectServer],
guid: str,
) -> OAuthIntegration:
with RSConnectClient(connect_server) as client:
return client.oauth_integration_get(guid)


def create_oauth_integration(
connect_server: Union[RSConnectServer, SPCSConnectServer],
template: str,
config: dict[str, object],
name: Optional[str] = None,
description: Optional[str] = None,
user_guids: Optional[list[str]] = None,
group_guids: Optional[list[str]] = None,
) -> OAuthIntegration:
permissions: list[OAuthIntegrationPermission] = []
for g in user_guids or []:
permissions.append({"user_guid": g, "group_guid": None})
for g in group_guids or []:
permissions.append({"user_guid": None, "group_guid": g})

body: OAuthIntegrationInput = {
"template": template,
"config": config,
}
if name is not None:
body["name"] = name
if description is not None:
body["description"] = description
if permissions:
body["permissions"] = permissions

with RSConnectClient(connect_server) as client:
return client.oauth_integration_create(body)


def update_oauth_integration(
connect_server: Union[RSConnectServer, SPCSConnectServer],
guid: str,
config: Optional[dict[str, object]] = None,
name: Optional[str] = None,
description: Optional[str] = None,
user_guids: Optional[list[str]] = None,
group_guids: Optional[list[str]] = None,
) -> OAuthIntegration:
with RSConnectClient(connect_server) as client:
body: OAuthIntegrationUpdate = {}
if name is not None:
body["name"] = name
if description is not None:
body["description"] = description
if config is not None:
existing = client.oauth_integration_get(guid)
merged_config = dict(existing["config"])
merged_config.update(config)
body["config"] = merged_config

permissions: list[OAuthIntegrationPermission] = []
if user_guids:
for g in user_guids:
permissions.append({"user_guid": g, "group_guid": None})
if group_guids:
for g in group_guids:
permissions.append({"user_guid": None, "group_guid": g})
if permissions:
body["permissions"] = permissions

return client.oauth_integration_update(guid, body)


def delete_oauth_integration(
connect_server: Union[RSConnectServer, SPCSConnectServer],
guid: str,
) -> None:
with RSConnectClient(connect_server) as client:
client.oauth_integration_delete(guid)


def list_oauth_templates(
connect_server: Union[RSConnectServer, SPCSConnectServer],
) -> list[OAuthTemplate]:
with RSConnectClient(connect_server) as client:
return client.oauth_template_list()


def get_oauth_template(
connect_server: Union[RSConnectServer, SPCSConnectServer],
key: str,
) -> OAuthTemplate:
with RSConnectClient(connect_server) as client:
return client.oauth_template_get(key)
38 changes: 38 additions & 0 deletions rsconnect/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@
EnvironmentUpdateInput,
EnvironmentV1,
ListEntryOutputDTO,
OAuthIntegration,
OAuthIntegrationInput,
OAuthIntegrationUpdate,
OAuthTemplate,
PyInfo,
ServerSettings,
TaskStatusV1,
Expand Down Expand Up @@ -782,6 +786,40 @@ def environment_permission_delete(self, env_guid: str, permission_guid: str) ->
)
self._server.handle_bad_response(response, is_httpresponse=True)

def oauth_integration_list(self) -> list[OAuthIntegration]:
response = cast(Union[List[OAuthIntegration], HTTPResponse], self.get("v1/oauth/integrations"))
response = self._server.handle_bad_response(response)
return response

def oauth_integration_get(self, guid: str) -> OAuthIntegration:
response = cast(Union[OAuthIntegration, HTTPResponse], self.get(f"v1/oauth/integrations/{guid}"))
response = self._server.handle_bad_response(response)
return response

def oauth_integration_create(self, body: OAuthIntegrationInput) -> OAuthIntegration:
response = cast(Union[OAuthIntegration, HTTPResponse], self.post("v1/oauth/integrations", body=body))
response = self._server.handle_bad_response(response)
return response

def oauth_integration_update(self, guid: str, body: OAuthIntegrationUpdate) -> OAuthIntegration:
response = cast(Union[OAuthIntegration, HTTPResponse], self.patch(f"v1/oauth/integrations/{guid}", body=body))
response = self._server.handle_bad_response(response)
return response

def oauth_integration_delete(self, guid: str) -> None:
response = cast(HTTPResponse, self.delete(f"v1/oauth/integrations/{guid}", decode_response=False))
self._server.handle_bad_response(response, is_httpresponse=True)

def oauth_template_list(self) -> list[OAuthTemplate]:
response = cast(Union[List[OAuthTemplate], HTTPResponse], self.get("v1/oauth/templates"))
response = self._server.handle_bad_response(response)
return response

def oauth_template_get(self, key: str) -> OAuthTemplate:
response = cast(Union[OAuthTemplate, HTTPResponse], self.get(f"v1/oauth/templates/{key}"))
response = self._server.handle_bad_response(response)
return response

def task_get(
self,
task_id: str,
Expand Down
Loading
Loading