diff --git a/platformio/account/commands/register.py b/platformio/account/commands/register.py index b5ec532d..6fc20ce6 100644 --- a/platformio/account/commands/register.py +++ b/platformio/account/commands/register.py @@ -15,7 +15,7 @@ import click from platformio.account.client import AccountClient -from platformio.account.helpers import ( +from platformio.account.validate import ( validate_email, validate_password, validate_username, diff --git a/platformio/account/commands/update.py b/platformio/account/commands/update.py index e198867d..b3868938 100644 --- a/platformio/account/commands/update.py +++ b/platformio/account/commands/update.py @@ -15,7 +15,7 @@ import click from platformio.account.client import AccountClient, AccountNotAuthorized -from platformio.account.helpers import validate_email, validate_username +from platformio.account.validate import validate_email, validate_username @click.command("update", short_help="Update profile information") diff --git a/platformio/account/org/commands/create.py b/platformio/account/org/commands/create.py index f4b7737a..b0c86bf5 100644 --- a/platformio/account/org/commands/create.py +++ b/platformio/account/org/commands/create.py @@ -15,7 +15,7 @@ import click from platformio.account.client import AccountClient -from platformio.account.helpers import validate_email, validate_orgname +from platformio.account.validate import validate_email, validate_orgname @click.command("create", short_help="Create a new organization") diff --git a/platformio/account/org/commands/update.py b/platformio/account/org/commands/update.py index 7a7d2b4c..9da9564c 100644 --- a/platformio/account/org/commands/update.py +++ b/platformio/account/org/commands/update.py @@ -15,7 +15,7 @@ import click from platformio.account.client import AccountClient -from platformio.account.helpers import validate_email, validate_orgname +from platformio.account.validate import validate_email, validate_orgname @click.command("update", short_help="Update organization") diff --git a/platformio/account/team/__init__.py b/platformio/account/team/__init__.py new file mode 100644 index 00000000..b0514903 --- /dev/null +++ b/platformio/account/team/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2014-present PlatformIO +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/platformio/account/team/cli.py b/platformio/account/team/cli.py new file mode 100644 index 00000000..feffa7b3 --- /dev/null +++ b/platformio/account/team/cli.py @@ -0,0 +1,38 @@ +# Copyright (c) 2014-present PlatformIO +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import click + +from platformio.account.team.commands.add import team_add_cmd +from platformio.account.team.commands.create import team_create_cmd +from platformio.account.team.commands.destroy import team_destroy_cmd +from platformio.account.team.commands.list import team_list_cmd +from platformio.account.team.commands.remove import team_remove_cmd +from platformio.account.team.commands.update import team_update_cmd + + +@click.group( + "team", + commands=[ + team_add_cmd, + team_create_cmd, + team_destroy_cmd, + team_list_cmd, + team_remove_cmd, + team_update_cmd, + ], + short_help="Manage organization teams", +) +def cli(): + pass diff --git a/platformio/account/team/commands/__init__.py b/platformio/account/team/commands/__init__.py new file mode 100644 index 00000000..b0514903 --- /dev/null +++ b/platformio/account/team/commands/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2014-present PlatformIO +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/platformio/account/team/commands/add.py b/platformio/account/team/commands/add.py new file mode 100644 index 00000000..854675e6 --- /dev/null +++ b/platformio/account/team/commands/add.py @@ -0,0 +1,38 @@ +# Copyright (c) 2014-present PlatformIO +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import click + +from platformio.account.client import AccountClient +from platformio.account.validate import validate_orgname_teamname + + +@click.command("add", short_help="Add a new member to team") +@click.argument( + "orgname_teamname", + metavar="ORGNAME:TEAMNAME", + callback=lambda _, __, value: validate_orgname_teamname(value), +) +@click.argument( + "username", +) +def team_add_cmd(orgname_teamname, username): + orgname, teamname = orgname_teamname.split(":", 1) + client = AccountClient() + client.add_team_member(orgname, teamname, username) + return click.secho( + "The new member %s has been successfully added to the %s team." + % (username, teamname), + fg="green", + ) diff --git a/platformio/account/team/commands/create.py b/platformio/account/team/commands/create.py new file mode 100644 index 00000000..891d33b4 --- /dev/null +++ b/platformio/account/team/commands/create.py @@ -0,0 +1,39 @@ +# Copyright (c) 2014-present PlatformIO +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import click + +from platformio.account.client import AccountClient +from platformio.account.validate import validate_orgname_teamname + + +@click.command("create", short_help="Create a new team") +@click.argument( + "orgname_teamname", + metavar="ORGNAME:TEAMNAME", + callback=lambda _, __, value: validate_orgname_teamname( + value, teamname_validate=True + ), +) +@click.option( + "--description", +) +def team_create_cmd(orgname_teamname, description): + orgname, teamname = orgname_teamname.split(":", 1) + client = AccountClient() + client.create_team(orgname, teamname, description) + return click.secho( + "The team %s has been successfully created." % teamname, + fg="green", + ) diff --git a/platformio/account/team/commands/destroy.py b/platformio/account/team/commands/destroy.py new file mode 100644 index 00000000..7cd084a1 --- /dev/null +++ b/platformio/account/team/commands/destroy.py @@ -0,0 +1,40 @@ +# Copyright (c) 2014-present PlatformIO +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import click + +from platformio.account.client import AccountClient +from platformio.account.validate import validate_orgname_teamname + + +@click.command("destroy", short_help="Destroy a team") +@click.argument( + "orgname_teamname", + metavar="ORGNAME:TEAMNAME", + callback=lambda _, __, value: validate_orgname_teamname(value), +) +def team_destroy_cmd(orgname_teamname): + orgname, teamname = orgname_teamname.split(":", 1) + click.confirm( + click.style( + "Are you sure you want to destroy the %s team?" % teamname, fg="yellow" + ), + abort=True, + ) + client = AccountClient() + client.destroy_team(orgname, teamname) + return click.secho( + "The team %s has been successfully destroyed." % teamname, + fg="green", + ) diff --git a/platformio/account/team/commands/list.py b/platformio/account/team/commands/list.py new file mode 100644 index 00000000..e41e872d --- /dev/null +++ b/platformio/account/team/commands/list.py @@ -0,0 +1,59 @@ +# Copyright (c) 2014-present PlatformIO +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json + +import click +from tabulate import tabulate + +from platformio.account.client import AccountClient + + +@click.command("list", short_help="List teams") +@click.argument("orgname", required=False) +@click.option("--json-output", is_flag=True) +def team_list_cmd(orgname, json_output): + client = AccountClient() + data = {} + if not orgname: + for item in client.list_orgs(): + teams = client.list_teams(item.get("orgname")) + data[item.get("orgname")] = teams + else: + teams = client.list_teams(orgname) + data[orgname] = teams + if json_output: + return click.echo(json.dumps(data[orgname] if orgname else data)) + if not any(data.values()): + return click.secho("You do not have any teams.", fg="yellow") + for org_name in data: + for team in data[org_name]: + click.echo() + click.secho("%s:%s" % (org_name, team.get("name")), fg="cyan") + click.echo("-" * len("%s:%s" % (org_name, team.get("name")))) + table_data = [] + if team.get("description"): + table_data.append(("Description:", team.get("description"))) + table_data.append( + ( + "Members:", + ", ".join( + (member.get("username") for member in team.get("members")) + ) + if team.get("members") + else "-", + ) + ) + click.echo(tabulate(table_data, tablefmt="plain")) + return click.echo() diff --git a/platformio/account/team/commands/remove.py b/platformio/account/team/commands/remove.py new file mode 100644 index 00000000..73c79602 --- /dev/null +++ b/platformio/account/team/commands/remove.py @@ -0,0 +1,36 @@ +# Copyright (c) 2014-present PlatformIO +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import click + +from platformio.account.client import AccountClient +from platformio.account.validate import validate_orgname_teamname + + +@click.command("remove", short_help="Remove a member from team") +@click.argument( + "orgname_teamname", + metavar="ORGNAME:TEAMNAME", + callback=lambda _, __, value: validate_orgname_teamname(value), +) +@click.argument("username") +def team_remove_cmd(orgname_teamname, username): + orgname, teamname = orgname_teamname.split(":", 1) + client = AccountClient() + client.remove_team_member(orgname, teamname, username) + return click.secho( + "The %s member has been successfully removed from the %s team." + % (username, teamname), + fg="green", + ) diff --git a/platformio/account/team/commands/update.py b/platformio/account/team/commands/update.py new file mode 100644 index 00000000..3ead0fed --- /dev/null +++ b/platformio/account/team/commands/update.py @@ -0,0 +1,55 @@ +# Copyright (c) 2014-present PlatformIO +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import click + +from platformio.account.client import AccountClient +from platformio.account.validate import validate_orgname_teamname, validate_teamname + + +@click.command("update", short_help="Update team") +@click.argument( + "orgname_teamname", + metavar="ORGNAME:TEAMNAME", + callback=lambda _, __, value: validate_orgname_teamname(value), +) +@click.option( + "--name", + callback=lambda _, __, value: validate_teamname(value), + help="A new team name", +) +@click.option( + "--description", +) +def team_update_cmd(orgname_teamname, **kwargs): + orgname, teamname = orgname_teamname.split(":", 1) + client = AccountClient() + team = client.get_team(orgname, teamname) + del team["id"] + del team["members"] + new_team = team.copy() + if not any(kwargs.values()): + for field in team: + new_team[field] = click.prompt( + field.replace("_", " ").capitalize(), default=team[field] + ) + if field == "name": + validate_teamname(new_team[field]) + else: + new_team.update({key: value for key, value in kwargs.items() if value}) + client.update_team(orgname, teamname, new_team) + return click.secho( + "The team %s has been successfully updated." % teamname, + fg="green", + ) diff --git a/platformio/account/validate.py b/platformio/account/validate.py new file mode 100644 index 00000000..69efb753 --- /dev/null +++ b/platformio/account/validate.py @@ -0,0 +1,79 @@ +# Copyright (c) 2014-present PlatformIO +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re + +import click + + +def validate_username(value, field="username"): + value = str(value).strip() + if not re.match(r"^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,37}$", value, flags=re.I): + raise click.BadParameter( + "Invalid %s format. " + "%s must contain only alphanumeric characters " + "or single hyphens, cannot begin or end with a hyphen, " + "and must not be longer than 38 characters." + % (field.lower(), field.capitalize()) + ) + return value + + +def validate_email(value): + value = str(value).strip() + if not re.match(r"^[a-z\d_.+-]+@[a-z\d\-]+\.[a-z\d\-.]+$", value, flags=re.I): + raise click.BadParameter("Invalid email address") + return value + + +def validate_password(value): + value = str(value).strip() + if not re.match(r"^(?=.*[a-z])(?=.*\d).{8,}$", value): + raise click.BadParameter( + "Invalid password format. " + "Password must contain at least 8 characters" + " including a number and a lowercase letter" + ) + return value + + +def validate_orgname(value): + return validate_username(value, "Organization name") + + +def validate_orgname_teamname(value, teamname_validate=False): + if ":" not in value: + raise click.BadParameter( + "Please specify organization and team name in the next" + " format - orgname:teamname. For example, mycompany:DreamTeam" + ) + teamname = str(value.strip().split(":", 1)[1]) + if teamname_validate: + validate_teamname(teamname) + return value + + +def validate_teamname(value): + if not value: + return value + value = str(value).strip() + if not re.match(r"^[a-z\d](?:[a-z\d]|[\-_ ](?=[a-z\d])){0,19}$", value, flags=re.I): + raise click.BadParameter( + "Invalid team name format. " + "Team name must only contain alphanumeric characters, " + "single hyphens, underscores, spaces. It can not " + "begin or end with a hyphen or a underscore and must" + " not be longer than 20 characters." + ) + return value diff --git a/platformio/commands/team.py b/platformio/commands/team.py deleted file mode 100644 index aac68d1e..00000000 --- a/platformio/commands/team.py +++ /dev/null @@ -1,212 +0,0 @@ -# Copyright (c) 2014-present PlatformIO -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# pylint: disable=unused-argument - -import json -import re - -import click -from tabulate import tabulate - -from platformio.account.client import AccountClient - - -def validate_orgname_teamname(value, teamname_validate=False): - if ":" not in value: - raise click.BadParameter( - "Please specify organization and team name in the next" - " format - orgname:teamname. For example, mycompany:DreamTeam" - ) - teamname = str(value.strip().split(":", 1)[1]) - if teamname_validate: - validate_teamname(teamname) - return value - - -def validate_teamname(value): - if not value: - return value - value = str(value).strip() - if not re.match(r"^[a-z\d](?:[a-z\d]|[\-_ ](?=[a-z\d])){0,19}$", value, flags=re.I): - raise click.BadParameter( - "Invalid team name format. " - "Team name must only contain alphanumeric characters, " - "single hyphens, underscores, spaces. It can not " - "begin or end with a hyphen or a underscore and must" - " not be longer than 20 characters." - ) - return value - - -@click.group("team", short_help="Manage organization teams") -def cli(): - pass - - -@cli.command("create", short_help="Create a new team") -@click.argument( - "orgname_teamname", - metavar="ORGNAME:TEAMNAME", - callback=lambda _, __, value: validate_orgname_teamname( - value, teamname_validate=True - ), -) -@click.option( - "--description", -) -def team_create(orgname_teamname, description): - orgname, teamname = orgname_teamname.split(":", 1) - client = AccountClient() - client.create_team(orgname, teamname, description) - return click.secho( - "The team %s has been successfully created." % teamname, - fg="green", - ) - - -@cli.command("list", short_help="List teams") -@click.argument("orgname", required=False) -@click.option("--json-output", is_flag=True) -def team_list(orgname, json_output): - client = AccountClient() - data = {} - if not orgname: - for item in client.list_orgs(): - teams = client.list_teams(item.get("orgname")) - data[item.get("orgname")] = teams - else: - teams = client.list_teams(orgname) - data[orgname] = teams - if json_output: - return click.echo(json.dumps(data[orgname] if orgname else data)) - if not any(data.values()): - return click.secho("You do not have any teams.", fg="yellow") - for org_name in data: - for team in data[org_name]: - click.echo() - click.secho("%s:%s" % (org_name, team.get("name")), fg="cyan") - click.echo("-" * len("%s:%s" % (org_name, team.get("name")))) - table_data = [] - if team.get("description"): - table_data.append(("Description:", team.get("description"))) - table_data.append( - ( - "Members:", - ", ".join( - (member.get("username") for member in team.get("members")) - ) - if team.get("members") - else "-", - ) - ) - click.echo(tabulate(table_data, tablefmt="plain")) - return click.echo() - - -@cli.command("update", short_help="Update team") -@click.argument( - "orgname_teamname", - metavar="ORGNAME:TEAMNAME", - callback=lambda _, __, value: validate_orgname_teamname(value), -) -@click.option( - "--name", - callback=lambda _, __, value: validate_teamname(value), - help="A new team name", -) -@click.option( - "--description", -) -def team_update(orgname_teamname, **kwargs): - orgname, teamname = orgname_teamname.split(":", 1) - client = AccountClient() - team = client.get_team(orgname, teamname) - del team["id"] - del team["members"] - new_team = team.copy() - if not any(kwargs.values()): - for field in team: - new_team[field] = click.prompt( - field.replace("_", " ").capitalize(), default=team[field] - ) - if field == "name": - validate_teamname(new_team[field]) - else: - new_team.update({key: value for key, value in kwargs.items() if value}) - client.update_team(orgname, teamname, new_team) - return click.secho( - "The team %s has been successfully updated." % teamname, - fg="green", - ) - - -@cli.command("destroy", short_help="Destroy a team") -@click.argument( - "orgname_teamname", - metavar="ORGNAME:TEAMNAME", - callback=lambda _, __, value: validate_orgname_teamname(value), -) -def team_destroy(orgname_teamname): - orgname, teamname = orgname_teamname.split(":", 1) - click.confirm( - click.style( - "Are you sure you want to destroy the %s team?" % teamname, fg="yellow" - ), - abort=True, - ) - client = AccountClient() - client.destroy_team(orgname, teamname) - return click.secho( - "The team %s has been successfully destroyed." % teamname, - fg="green", - ) - - -@cli.command("add", short_help="Add a new member to team") -@click.argument( - "orgname_teamname", - metavar="ORGNAME:TEAMNAME", - callback=lambda _, __, value: validate_orgname_teamname(value), -) -@click.argument( - "username", -) -def team_add_member(orgname_teamname, username): - orgname, teamname = orgname_teamname.split(":", 1) - client = AccountClient() - client.add_team_member(orgname, teamname, username) - return click.secho( - "The new member %s has been successfully added to the %s team." - % (username, teamname), - fg="green", - ) - - -@cli.command("remove", short_help="Remove a member from team") -@click.argument( - "orgname_teamname", - metavar="ORGNAME:TEAMNAME", - callback=lambda _, __, value: validate_orgname_teamname(value), -) -@click.argument("username") -def team_remove_owner(orgname_teamname, username): - orgname, teamname = orgname_teamname.split(":", 1) - client = AccountClient() - client.remove_team_member(orgname, teamname, username) - return click.secho( - "The %s member has been successfully removed from the %s team." - % (username, teamname), - fg="green", - ) diff --git a/platformio/registry/access/validate.py b/platformio/registry/access/validate.py index 20aae07e..71ca5a92 100644 --- a/platformio/registry/access/validate.py +++ b/platformio/registry/access/validate.py @@ -16,8 +16,7 @@ import re import click -from platformio.account.validate import validate_username -from platformio.commands.team import validate_orgname_teamname +from platformio.account.validate import validate_orgname_teamname, validate_username def validate_urn(value): diff --git a/tests/commands/test_account_org_team.py b/tests/commands/test_account_org_team.py index eb0c3edf..4f23ff9b 100644 --- a/tests/commands/test_account_org_team.py +++ b/tests/commands/test_account_org_team.py @@ -23,7 +23,7 @@ import requests from platformio.account.cli import cli as cmd_account from platformio.account.org.cli import cli as cmd_org -from platformio.commands.team import cli as cmd_team +from platformio.account.team.cli import cli as cmd_team pytestmark = pytest.mark.skipif( not all(