Skip to content

Commit

Permalink
Update cookiecutter to integration standards
Browse files Browse the repository at this point in the history
* Proper `.env.example` files
* Update vscode launch configuration with new generated integration
* Add initial static examples to see the integration quickly in Port,
  including example blueprints and mappings
* Add unit test stub
  • Loading branch information
erikzaadi committed Aug 22, 2024
1 parent 63b6f27 commit 688ac66
Show file tree
Hide file tree
Showing 15 changed files with 245 additions and 107 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ define run_checks
cd $1; \
poetry check || exit_code=$$?;\
mypy . --exclude '/\.venv/' || exit_code=$$?; \
ruff . || exit_code=$$?; \
ruff check . || exit_code=$$?; \
black --check . || exit_code=$$?; \
yamllint . || exit_code=$$?; \
if [ $$exit_code -eq 1 ]; then \
Expand Down Expand Up @@ -55,6 +55,7 @@ install:
$(call install_precommit)

test/all: test
$(ACTIVATE) && \
pytest --import-mode=importlib -n auto ./port_ocean/tests ./integrations/*/tests

install/all: install
Expand Down
176 changes: 95 additions & 81 deletions poetry.lock

Large diffs are not rendered by default.

42 changes: 38 additions & 4 deletions port_ocean/cli/commands/new.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,39 @@
# -*- coding: utf-8 -*-

import click
import json
from cookiecutter.main import cookiecutter # type: ignore
import os

from port_ocean.cli.commands.main import cli_start, print_logo, console
from port_ocean.cli.utils import cli_root_path


def add_vscode_configuration(result: str, name: str) -> None:
vscode_entry_root_path = "${workspaceFolder}/integrations/" + name
new_vscode_entry = {
"console": "integratedTerminal",
"cwd": vscode_entry_root_path,
"envFile": f"{vscode_entry_root_path}/.env",
"justMyCode": True,
"name": f"Run {name} integration",
"program": f"{vscode_entry_root_path}/debug.py",
"python": f"{vscode_entry_root_path}/.venv/bin/python",
"request": "launch",
"type": "debugpy",
}

vs_code_json_path = os.path.join(os.path.dirname(result), "../.vscode/launch.json")
if not os.path.exists(vs_code_json_path):
return
vs_code_json = json.load(open(vs_code_json_path, "r"))
vs_code_json["configurations"].append(new_vscode_entry)

with open(vs_code_json_path, "w") as vs_code_json_file:
json.dump(vs_code_json, vs_code_json_file, indent=2)
vs_code_json_file.write("\n")


@cli_start.command()
@click.argument("path", default=".", type=click.Path(exists=True))
@click.option(
Expand Down Expand Up @@ -37,6 +64,9 @@ def new(path: str, is_private_integration: bool) -> None:
)
name = result.split("/")[-1]

if not is_private_integration:
add_vscode_configuration(result, name)

console.print(
"\n🌊 Ahoy, Captain! Your project is ready to set sail into the vast ocean of possibilities!",
style="bold",
Expand All @@ -47,10 +77,14 @@ def new(path: str, is_private_integration: bool) -> None:
f"▶️ [bold][blue]cd {path}/{name} && make install && . .venv/bin/activate[/blue][/bold]\n"
)
console.print(
"⚓️ Set sail with [blue]Ocean[/blue]: Run [bold][blue]ocean sail[/blue] <path_to_integration>[/bold] to run the project using Ocean.\n"
f"▶️ [bold][blue]ocean sail {path}/{name}[/blue][/bold] \n"
f"⚓️ Copy example env file: Run [bold][blue]cp {path}/{name}.env.example {path}/{name}/.env [/blue][/bold] and set your port credentials in the created file.\n"
)
console.print(
"⚓️ Smooth sailing with [blue]Make[/blue]: Alternatively, you can run [bold][blue]make run[/blue][/bold] to launch your project using Make. \n"
f"▶️ [bold][blue]make run {path}/{name}[/blue][/bold]"
"⚓️ Set sail with [blue]Ocean[/blue]: Run [bold][blue]ocean sail[/blue] <path_to_integration>[/bold] to run the project using Ocean.\n"
f"▶️ [bold][blue]ocean sail {path}/{name}[/blue][/bold] \n"
)
if not is_private_integration:
console.print(
"⚓️ Smooth sailing with [blue]Make[/blue]: Alternatively, you can run [bold][blue]make run[/blue][/bold] to launch your project using Make. \n"
f"▶️ [bold][blue]make run {path}/{name}[/blue][/bold]"
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
OCEAN__PORT__CLIENT_ID="<port-client-id>"
OCEAN__PORT__CLIENT_SECRET="<port-client-secret>"
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
[
{
"identifier": "{{ cookiecutter.integration_slug }}ExampleBlueprint",
"title": "{{ cookiecutter.integration_name }} Example",
"icon": "Blueprint",
"schema": {
"properties": {
"status": {
"type": "string",
"enum": [
"VALID",
"FAILED"
],
"enumColors": {
"VALID": "green",
"FAILED": "red"
},
"title": "Status"
},
"text": {
"type": "string",
"title": "Text"
},
"component": {
"type": "string",
"title": "Component"
},
"service": {
"type": "string",
"title": "Service"
},
"score": {
"type": "number",
"title": "Score"
}
},
"required": []
},
"relations": {}
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
resources:
- kind: {{ cookiecutter.integration_slug}}-example-kind
selector:
query: 'true'
port:
entity:
mappings:
identifier: .my_custom_id
title: '(.my_component + " @ " + .my_service)'
blueprint: '"{{ cookiecutter.integration_slug }}ExampleBlueprint"'
properties:
status: .my_enum
text: .my_custom_text
component: .my_component
service: .my_service
score: .my_special_score
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
description: {{cookiecutter.integration_name}} integration for Port Ocean
icon: Cookiecutter # Should be one of the available icons in Port
icon: Cookiecutter # Should be one of the available icons in Port
features:
- type: exporter
section: Git Providers # Should be one of the available sections in Port
section: Under Development # Should be one of the available sections in Port
resources:
- kind: <ResourceName1>
- kind: <ResourceName2>
- kind: {{ cookiecutter.integration_slug }}-example-kind
# - kind: <ResourceName2>
configurations:
- name: myGitToken
required: true
- name: my{{ cookiecutter.integration_slug}}Token
# required: true
type: string
sensitive: true
- name: someApplicationUrl
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Changelog
# Changelog - Ocean - {{ cookiecutter.integration_slug }}

All notable changes to this project will be documented in this file.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Contributing to Ocean - {{ cookiecutter.integration_slug }}

## Running locally

#### NOTE: Add your own instructions of how to run {{ cookiecutter.integration_slug }}

This could be any gotcha's such as rate limiting, how to setup credentials and so forth
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ lint:
run:
$(ACTIVATE) && ocean sail

test: lint
$(ACTIVATE) && poetry run pytest
test:
$(ACTIVATE) && poetry run pytest -n auto

clean:
@find . -name '.venv' -type d -exec rm -rf {} \;
Expand All @@ -71,4 +71,4 @@ clean:
rm -rf htmlcov
rm -rf .tox/
rm -rf docs/_build
rm -rf dist/
rm -rf dist/
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
*
!.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,21 @@ async def on_resync(kind: str) -> list[dict[Any, Any]]:
# return [{"some_project_key": "someProjectValue", ...}]
# if kind == "issues":
# return [{"some_issue_key": "someIssueValue", ...}]

# Initial stub to show complete flow, replace this with your own logic
if kind == "{{ cookiecutter.integration_slug }}-example-kind":
return [
{
"my_custom_id": f"id_{x}",
"my_custom_text": f"very long text with {x} in it",
"my_special_score": x * 32 % 3,
"my_component": f"component-{x}",
"my_service": f"service-{x %2}",
"my_enum": "VALID" if x % 2 == 0 else "FAILED",
}
for x in range(25)
]

return []


Expand All @@ -38,4 +53,4 @@ async def on_resync(kind: str) -> list[dict[Any, Any]]:
async def on_start() -> None:
# Something to do when the integration starts
# For example create a client to query 3rd party services - GitHub, Jira, etc...
print("Starting integration")
print("Starting {{ cookiecutter.integration_slug }} integration")
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@ python = "^3.11"
port_ocean = { version = "^{% version %}", extras = ["cli"] }

[tool.poetry.group.dev.dependencies]
# Uncomment this if you want to debug the ocean core together with your integration
# port_ocean = { path = '../../', develop = true, extras = ['all'] }
pytest = "^7.2"
pytest-xdist = "^3.6.1"
pre-commit = "^3.7.1"
requests = "^2.32.3"
black = "^23.3.0"
mypy = "^1.3.0"
ruff = "^0.0.278"
pylint = "^2.17.4"
towncrier = "^23.6.0"
pytest-xdist = "^3.6.1"

[tool.towncrier]
directory = "changelog"
Expand Down Expand Up @@ -54,8 +58,8 @@ underlines = [""]
showcontent = true

[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

[tool.mypy]
exclude = [
Expand Down
13 changes: 8 additions & 5 deletions port_ocean/config/settings.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Literal, Type
from typing import Any, Literal, Type, cast

from pydantic import Extra, AnyHttpUrl, parse_obj_as, parse_raw_as
from pydantic.class_validators import root_validator, validator
Expand Down Expand Up @@ -55,9 +55,8 @@ def root_validator(cls, values: dict[str, Any]) -> dict[str, Any]:
integ_type = get_integration_name()

values["type"] = integ_type.lower() if integ_type else None
values["identifier"] = values.get(
"identifier", f"my-{integ_type}-integration".lower()
)
if not values.get("identifier"):
values["identifier"] = f"my-{integ_type}-integration".lower()

return values

Expand All @@ -71,7 +70,9 @@ class IntegrationConfiguration(BaseOceanSettings, extra=Extra.allow):
client_timeout: int = 30
send_raw_data_examples: bool = True
port: PortSettings
event_listener: EventListenerSettingsType
event_listener: EventListenerSettingsType = Field(
default=cast(EventListenerSettingsType, {"type": "POLLING"})
)
# If an identifier or type is not provided, it will be generated based on the integration name
integration: IntegrationSettings = Field(
default_factory=lambda: IntegrationSettings(type="", identifier="")
Expand Down Expand Up @@ -114,3 +115,5 @@ def validate_runtime(cls, runtime: Runtime) -> Runtime:
raise ValueError("This integration can't be ran as Saas")

return runtime

return runtime
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ classifiers = [
ocean = "port_ocean.cli.cli:cli_start"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.masonry.api"
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

[tool.poetry.dependencies]
python = "^3.11"
Expand Down

0 comments on commit 688ac66

Please sign in to comment.