wayscloud-cli 0.1.0__tar.gz → 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wayscloud_cli-0.1.1/PKG-INFO +147 -0
- wayscloud_cli-0.1.1/README.md +123 -0
- wayscloud_cli-0.1.1/pyproject.toml +36 -0
- {wayscloud_cli-0.1.0 → wayscloud_cli-0.1.1}/setup.cfg +4 -0
- wayscloud_cli-0.1.1/tests/test_smoke.py +146 -0
- {wayscloud_cli-0.1.0 → wayscloud_cli-0.1.1}/wayscloud_cli/__init__.py +1 -1
- {wayscloud_cli-0.1.0 → wayscloud_cli-0.1.1}/wayscloud_cli/__main__.py +20 -6
- wayscloud_cli-0.1.1/wayscloud_cli/commands/__init__.py +0 -0
- wayscloud_cli-0.1.1/wayscloud_cli/commands/app.py +136 -0
- wayscloud_cli-0.1.1/wayscloud_cli/commands/db.py +103 -0
- wayscloud_cli-0.1.1/wayscloud_cli/commands/dns.py +171 -0
- wayscloud_cli-0.1.1/wayscloud_cli/commands/iot.py +165 -0
- {wayscloud_cli-0.1.0 → wayscloud_cli-0.1.1}/wayscloud_cli/commands/login.py +25 -35
- wayscloud_cli-0.1.1/wayscloud_cli/commands/redis.py +127 -0
- wayscloud_cli-0.1.1/wayscloud_cli/commands/storage.py +87 -0
- {wayscloud_cli-0.1.0 → wayscloud_cli-0.1.1}/wayscloud_cli/commands/vps.py +116 -104
- wayscloud_cli-0.1.1/wayscloud_cli/sdk.py +52 -0
- wayscloud_cli-0.1.1/wayscloud_cli.egg-info/PKG-INFO +147 -0
- {wayscloud_cli-0.1.0 → wayscloud_cli-0.1.1}/wayscloud_cli.egg-info/SOURCES.txt +9 -1
- {wayscloud_cli-0.1.0 → wayscloud_cli-0.1.1}/wayscloud_cli.egg-info/requires.txt +1 -1
- {wayscloud_cli-0.1.0 → wayscloud_cli-0.1.1}/wayscloud_cli.egg-info/top_level.txt +1 -0
- wayscloud_cli-0.1.0/PKG-INFO +0 -39
- wayscloud_cli-0.1.0/README.md +0 -23
- wayscloud_cli-0.1.0/pyproject.toml +0 -28
- wayscloud_cli-0.1.0/wayscloud_cli/client.py +0 -94
- wayscloud_cli-0.1.0/wayscloud_cli.egg-info/PKG-INFO +0 -39
- {wayscloud_cli-0.1.0/wayscloud_cli/commands → wayscloud_cli-0.1.1/tests}/__init__.py +0 -0
- {wayscloud_cli-0.1.0 → wayscloud_cli-0.1.1}/wayscloud_cli/commands/shell.py +0 -0
- {wayscloud_cli-0.1.0 → wayscloud_cli-0.1.1}/wayscloud_cli/config.py +0 -0
- {wayscloud_cli-0.1.0 → wayscloud_cli-0.1.1}/wayscloud_cli/output.py +0 -0
- {wayscloud_cli-0.1.0 → wayscloud_cli-0.1.1}/wayscloud_cli.egg-info/dependency_links.txt +0 -0
- {wayscloud_cli-0.1.0 → wayscloud_cli-0.1.1}/wayscloud_cli.egg-info/entry_points.txt +0 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wayscloud-cli
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Command-line interface for WAYSCloud — manage VPS, DNS, databases, storage, apps, IoT, and more
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://wayscloud.services
|
|
7
|
+
Project-URL: Documentation, https://docs.wayscloud.services/cli
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Intended Audience :: System Administrators
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: wayscloud>=0.1.0
|
|
21
|
+
Requires-Dist: typer>=0.9
|
|
22
|
+
Requires-Dist: rich>=13.0
|
|
23
|
+
Requires-Dist: websockets>=12.0
|
|
24
|
+
|
|
25
|
+
# WAYSCloud CLI
|
|
26
|
+
|
|
27
|
+
Command-line interface for [WAYSCloud](https://wayscloud.services). Built on the [Python SDK](https://pypi.org/project/wayscloud/).
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install wayscloud-cli
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Authentication
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
cloud login --token wayscloud_pat_...
|
|
39
|
+
cloud whoami
|
|
40
|
+
cloud logout
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Priority: `--token` flag > `WAYSCLOUD_TOKEN` env var > `~/.wayscloud/credentials` file.
|
|
44
|
+
|
|
45
|
+
## Commands
|
|
46
|
+
|
|
47
|
+
### VPS
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
cloud vps list
|
|
51
|
+
cloud vps create --hostname web01 --plan vps-medium --region no --os ubuntu-24.04
|
|
52
|
+
cloud vps info <id>
|
|
53
|
+
cloud vps delete <id> --confirm
|
|
54
|
+
cloud vps start <id>
|
|
55
|
+
cloud vps stop <id>
|
|
56
|
+
cloud vps plans
|
|
57
|
+
cloud vps os-templates
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### DNS
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
cloud dns zones
|
|
64
|
+
cloud dns zones-create example.com
|
|
65
|
+
cloud dns records example.com
|
|
66
|
+
cloud dns records-create example.com --type A --name www --value 192.0.2.1
|
|
67
|
+
cloud dns records-delete example.com <record-id> --confirm
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Database
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
cloud db list
|
|
74
|
+
cloud db create mydb --type postgresql --tier standard
|
|
75
|
+
cloud db info postgresql mydb
|
|
76
|
+
cloud db delete postgresql mydb --confirm
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Redis
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
cloud redis list
|
|
83
|
+
cloud redis create myredis --plan redis-starter --region no
|
|
84
|
+
cloud redis info <id>
|
|
85
|
+
cloud redis delete <id> --confirm
|
|
86
|
+
cloud redis plans
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Storage
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
cloud storage buckets
|
|
93
|
+
cloud storage buckets-create my-bucket
|
|
94
|
+
cloud storage buckets-delete my-bucket --confirm
|
|
95
|
+
cloud storage credentials
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Apps
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
cloud app list
|
|
102
|
+
cloud app create my-app --plan app-basic --region eu
|
|
103
|
+
cloud app deploy <id> --image ghcr.io/org/app:latest
|
|
104
|
+
cloud app start <id>
|
|
105
|
+
cloud app stop <id>
|
|
106
|
+
cloud app delete <id> --confirm
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### IoT
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
cloud iot devices
|
|
113
|
+
cloud iot devices-create --device-id sensor-01 --name "Temperature Sensor"
|
|
114
|
+
cloud iot devices-info <device-id>
|
|
115
|
+
cloud iot devices-delete <device-id> --confirm
|
|
116
|
+
cloud iot groups
|
|
117
|
+
cloud iot groups-create --name "Floor 2"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Shell
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
cloud shell connect
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Output formats
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# Default: formatted tables
|
|
130
|
+
cloud vps list
|
|
131
|
+
|
|
132
|
+
# JSON (for scripting)
|
|
133
|
+
cloud vps list --json
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Requirements
|
|
137
|
+
|
|
138
|
+
- Python 3.9+
|
|
139
|
+
- wayscloud (SDK)
|
|
140
|
+
|
|
141
|
+
## Documentation
|
|
142
|
+
|
|
143
|
+
Full reference: [docs.wayscloud.services/cli](https://docs.wayscloud.services/cli)
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# WAYSCloud CLI
|
|
2
|
+
|
|
3
|
+
Command-line interface for [WAYSCloud](https://wayscloud.services). Built on the [Python SDK](https://pypi.org/project/wayscloud/).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install wayscloud-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Authentication
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
cloud login --token wayscloud_pat_...
|
|
15
|
+
cloud whoami
|
|
16
|
+
cloud logout
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Priority: `--token` flag > `WAYSCLOUD_TOKEN` env var > `~/.wayscloud/credentials` file.
|
|
20
|
+
|
|
21
|
+
## Commands
|
|
22
|
+
|
|
23
|
+
### VPS
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
cloud vps list
|
|
27
|
+
cloud vps create --hostname web01 --plan vps-medium --region no --os ubuntu-24.04
|
|
28
|
+
cloud vps info <id>
|
|
29
|
+
cloud vps delete <id> --confirm
|
|
30
|
+
cloud vps start <id>
|
|
31
|
+
cloud vps stop <id>
|
|
32
|
+
cloud vps plans
|
|
33
|
+
cloud vps os-templates
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### DNS
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
cloud dns zones
|
|
40
|
+
cloud dns zones-create example.com
|
|
41
|
+
cloud dns records example.com
|
|
42
|
+
cloud dns records-create example.com --type A --name www --value 192.0.2.1
|
|
43
|
+
cloud dns records-delete example.com <record-id> --confirm
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Database
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
cloud db list
|
|
50
|
+
cloud db create mydb --type postgresql --tier standard
|
|
51
|
+
cloud db info postgresql mydb
|
|
52
|
+
cloud db delete postgresql mydb --confirm
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Redis
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
cloud redis list
|
|
59
|
+
cloud redis create myredis --plan redis-starter --region no
|
|
60
|
+
cloud redis info <id>
|
|
61
|
+
cloud redis delete <id> --confirm
|
|
62
|
+
cloud redis plans
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Storage
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
cloud storage buckets
|
|
69
|
+
cloud storage buckets-create my-bucket
|
|
70
|
+
cloud storage buckets-delete my-bucket --confirm
|
|
71
|
+
cloud storage credentials
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Apps
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
cloud app list
|
|
78
|
+
cloud app create my-app --plan app-basic --region eu
|
|
79
|
+
cloud app deploy <id> --image ghcr.io/org/app:latest
|
|
80
|
+
cloud app start <id>
|
|
81
|
+
cloud app stop <id>
|
|
82
|
+
cloud app delete <id> --confirm
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### IoT
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
cloud iot devices
|
|
89
|
+
cloud iot devices-create --device-id sensor-01 --name "Temperature Sensor"
|
|
90
|
+
cloud iot devices-info <device-id>
|
|
91
|
+
cloud iot devices-delete <device-id> --confirm
|
|
92
|
+
cloud iot groups
|
|
93
|
+
cloud iot groups-create --name "Floor 2"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Shell
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
cloud shell connect
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Output formats
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# Default: formatted tables
|
|
106
|
+
cloud vps list
|
|
107
|
+
|
|
108
|
+
# JSON (for scripting)
|
|
109
|
+
cloud vps list --json
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Requirements
|
|
113
|
+
|
|
114
|
+
- Python 3.9+
|
|
115
|
+
- wayscloud (SDK)
|
|
116
|
+
|
|
117
|
+
## Documentation
|
|
118
|
+
|
|
119
|
+
Full reference: [docs.wayscloud.services/cli](https://docs.wayscloud.services/cli)
|
|
120
|
+
|
|
121
|
+
## License
|
|
122
|
+
|
|
123
|
+
MIT
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "wayscloud-cli"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Command-line interface for WAYSCloud — manage VPS, DNS, databases, storage, apps, IoT, and more"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"Intended Audience :: System Administrators",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.9",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"wayscloud>=0.1.0",
|
|
26
|
+
"typer>=0.9",
|
|
27
|
+
"rich>=13.0",
|
|
28
|
+
"websockets>=12.0",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://wayscloud.services"
|
|
33
|
+
Documentation = "https://docs.wayscloud.services/cli"
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
cloud = "wayscloud_cli.__main__:main"
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""CLI smoke tests — verify app structure, command registration, flags, and token resolution."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from unittest.mock import patch
|
|
6
|
+
|
|
7
|
+
from typer.testing import CliRunner
|
|
8
|
+
|
|
9
|
+
from wayscloud_cli.__main__ import app
|
|
10
|
+
from wayscloud_cli import __version__
|
|
11
|
+
from wayscloud_cli.config import resolve_token
|
|
12
|
+
from wayscloud_cli.output import set_json_mode, is_json_mode
|
|
13
|
+
|
|
14
|
+
runner = CliRunner()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# ── App structure ───────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
def test_app_has_help():
|
|
20
|
+
result = runner.invoke(app, ["--help"])
|
|
21
|
+
assert result.exit_code == 0
|
|
22
|
+
assert "WAYSCloud CLI" in result.output
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_version_flag():
|
|
26
|
+
result = runner.invoke(app, ["--version"])
|
|
27
|
+
assert result.exit_code == 0
|
|
28
|
+
assert __version__ in result.output
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ── Command groups registered ───────────────────────────────────
|
|
32
|
+
|
|
33
|
+
EXPECTED_GROUPS = ["auth", "vps", "dns", "storage", "db", "redis", "app", "iot", "shell"]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_all_command_groups_registered():
|
|
37
|
+
result = runner.invoke(app, ["--help"])
|
|
38
|
+
for group in EXPECTED_GROUPS:
|
|
39
|
+
assert group in result.output, f"Command group '{group}' not in help output"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_each_group_has_help():
|
|
43
|
+
for group in EXPECTED_GROUPS:
|
|
44
|
+
result = runner.invoke(app, [group, "--help"])
|
|
45
|
+
assert result.exit_code == 0, f"{group} --help failed with exit {result.exit_code}"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# ── Top-level shortcuts ─────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
def test_login_shortcut_exists():
|
|
51
|
+
result = runner.invoke(app, ["login", "--help"])
|
|
52
|
+
assert result.exit_code == 0
|
|
53
|
+
assert "token" in result.output.lower()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_whoami_shortcut_exists():
|
|
57
|
+
result = runner.invoke(app, ["whoami", "--help"])
|
|
58
|
+
assert result.exit_code == 0
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_logout_shortcut_exists():
|
|
62
|
+
result = runner.invoke(app, ["logout", "--help"])
|
|
63
|
+
assert result.exit_code == 0
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# ── Token resolution ────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
def test_explicit_token_wins(monkeypatch):
|
|
69
|
+
monkeypatch.setenv("WAYSCLOUD_TOKEN", "env_token")
|
|
70
|
+
assert resolve_token("explicit_token") == "explicit_token"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_env_var_used_when_no_explicit(monkeypatch):
|
|
74
|
+
monkeypatch.setenv("WAYSCLOUD_TOKEN", "env_token")
|
|
75
|
+
assert resolve_token(None) == "env_token"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_returns_none_when_nothing(monkeypatch, tmp_path):
|
|
79
|
+
monkeypatch.delenv("WAYSCLOUD_TOKEN", raising=False)
|
|
80
|
+
# Point credentials file to nonexistent path
|
|
81
|
+
monkeypatch.setattr("wayscloud_cli.config.CREDENTIALS_FILE", tmp_path / "nope")
|
|
82
|
+
assert resolve_token(None) is None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_reads_credentials_file(monkeypatch, tmp_path):
|
|
86
|
+
monkeypatch.delenv("WAYSCLOUD_TOKEN", raising=False)
|
|
87
|
+
cred_file = tmp_path / "credentials"
|
|
88
|
+
cred_file.write_text(json.dumps({"version": 1, "token": "file_token"}))
|
|
89
|
+
monkeypatch.setattr("wayscloud_cli.config.CREDENTIALS_FILE", cred_file)
|
|
90
|
+
assert resolve_token(None) == "file_token"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# ── Output modes ────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
def test_json_mode_toggle():
|
|
96
|
+
set_json_mode(False)
|
|
97
|
+
assert not is_json_mode()
|
|
98
|
+
set_json_mode(True)
|
|
99
|
+
assert is_json_mode()
|
|
100
|
+
set_json_mode(False)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_json_flag_accepted():
|
|
104
|
+
result = runner.invoke(app, ["--json", "--help"])
|
|
105
|
+
assert result.exit_code == 0
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# ── VPS commands exist with correct args ────────────────────────
|
|
109
|
+
|
|
110
|
+
def test_vps_list_help():
|
|
111
|
+
result = runner.invoke(app, ["vps", "list", "--help"])
|
|
112
|
+
assert result.exit_code == 0
|
|
113
|
+
assert "--token" in result.output
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_vps_create_requires_flags():
|
|
117
|
+
result = runner.invoke(app, ["vps", "create", "--help"])
|
|
118
|
+
assert result.exit_code == 0
|
|
119
|
+
for flag in ["--hostname", "--plan", "--region", "--os"]:
|
|
120
|
+
assert flag in result.output, f"Missing {flag} in vps create"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# ── DNS commands ────────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
def test_dns_records_create_has_type_flag():
|
|
126
|
+
result = runner.invoke(app, ["dns", "records-create", "--help"])
|
|
127
|
+
assert result.exit_code == 0
|
|
128
|
+
assert "--type" in result.output
|
|
129
|
+
assert "--value" in result.output
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# ── DB commands ─────────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
def test_db_create_has_type_flag():
|
|
135
|
+
result = runner.invoke(app, ["db", "create", "--help"])
|
|
136
|
+
assert result.exit_code == 0
|
|
137
|
+
assert "--type" in result.output
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# ── IoT commands ────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
def test_iot_devices_create_has_required_flags():
|
|
143
|
+
result = runner.invoke(app, ["iot", "devices-create", "--help"])
|
|
144
|
+
assert result.exit_code == 0
|
|
145
|
+
assert "--device-id" in result.output
|
|
146
|
+
assert "--name" in result.output
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""WAYSCloud CLI — cloud command."""
|
|
2
|
-
__version__ = "0.1.
|
|
2
|
+
__version__ = "0.1.1"
|
|
@@ -3,15 +3,23 @@ WAYSCloud CLI entry point.
|
|
|
3
3
|
|
|
4
4
|
Binary name: cloud (C21)
|
|
5
5
|
Package name: wayscloud-cli (C21)
|
|
6
|
+
|
|
7
|
+
Principle: CLI = provisioning + control, NOT observability.
|
|
6
8
|
"""
|
|
7
9
|
|
|
8
10
|
import typer
|
|
9
11
|
|
|
10
12
|
from . import __version__
|
|
11
13
|
from .output import set_json_mode, set_no_color
|
|
12
|
-
from .commands.login import app as login_app
|
|
14
|
+
from .commands.login import app as login_app, login, logout, whoami
|
|
13
15
|
from .commands.vps import app as vps_app
|
|
14
16
|
from .commands.shell import app as shell_app
|
|
17
|
+
from .commands.dns import app as dns_app
|
|
18
|
+
from .commands.storage import app as storage_app
|
|
19
|
+
from .commands.db import app as db_app
|
|
20
|
+
from .commands.redis import app as redis_app
|
|
21
|
+
from .commands.app import app as app_platform_app
|
|
22
|
+
from .commands.iot import app as iot_app
|
|
15
23
|
|
|
16
24
|
app = typer.Typer(
|
|
17
25
|
name="cloud",
|
|
@@ -43,14 +51,20 @@ def global_options(
|
|
|
43
51
|
|
|
44
52
|
|
|
45
53
|
# Register command groups
|
|
46
|
-
app.add_typer(login_app, name="auth", help="
|
|
54
|
+
app.add_typer(login_app, name="auth", help="Authentication")
|
|
47
55
|
app.add_typer(vps_app, name="vps", help="Virtual Private Servers")
|
|
48
|
-
app.add_typer(
|
|
56
|
+
app.add_typer(dns_app, name="dns", help="DNS zones and records")
|
|
57
|
+
app.add_typer(storage_app, name="storage", help="Object storage (S3)")
|
|
58
|
+
app.add_typer(db_app, name="db", help="Managed databases")
|
|
59
|
+
app.add_typer(redis_app, name="redis", help="Managed Redis")
|
|
60
|
+
app.add_typer(app_platform_app, name="app", help="App platform")
|
|
61
|
+
app.add_typer(iot_app, name="iot", help="IoT platform")
|
|
62
|
+
app.add_typer(shell_app, name="shell", help="CloudShell")
|
|
49
63
|
|
|
50
64
|
# Top-level shortcuts for login/logout/whoami
|
|
51
|
-
app.command("login")(
|
|
52
|
-
app.command("logout")(
|
|
53
|
-
app.command("whoami")(
|
|
65
|
+
app.command("login")(login)
|
|
66
|
+
app.command("logout")(logout)
|
|
67
|
+
app.command("whoami")(whoami)
|
|
54
68
|
|
|
55
69
|
|
|
56
70
|
def main():
|
|
File without changes
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""
|
|
2
|
+
cloud app commands — App Platform provisioning and control.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from ..sdk import get_client, sdk_call
|
|
9
|
+
from ..output import print_table, print_object, print_json, is_json_mode
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(help="App platform")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@app.command("list")
|
|
15
|
+
def list_apps(
|
|
16
|
+
token: Optional[str] = typer.Option(None, "--token", help="Override token"),
|
|
17
|
+
):
|
|
18
|
+
"""List your apps."""
|
|
19
|
+
c = get_client(token)
|
|
20
|
+
data = sdk_call(c.apps.list)
|
|
21
|
+
apps_list = data if isinstance(data, list) else data.get("apps", []) if isinstance(data, dict) else []
|
|
22
|
+
|
|
23
|
+
if is_json_mode():
|
|
24
|
+
print_json(apps_list)
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
if not apps_list:
|
|
28
|
+
print("No apps found.")
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
rows = [{"id": str(a.get("id", ""))[:14], "name": a.get("name", ""),
|
|
32
|
+
"status": a.get("status", ""), "plan": a.get("plan", a.get("plan_code", "")),
|
|
33
|
+
"url": a.get("default_url", ""), "created_at": a.get("created_at", "")}
|
|
34
|
+
for a in apps_list]
|
|
35
|
+
|
|
36
|
+
print_table(rows, [
|
|
37
|
+
("id", "ID", 14), ("name", "Name", 20), ("status", "Status", 10),
|
|
38
|
+
("plan", "Plan", 16), ("url", "URL", 30), ("created_at", "Created", 20),
|
|
39
|
+
])
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@app.command("create")
|
|
43
|
+
def create_app(
|
|
44
|
+
name: str = typer.Argument(..., help="App name"),
|
|
45
|
+
plan: str = typer.Option("app-basic", "--plan", help="Plan code"),
|
|
46
|
+
region: str = typer.Option("no", "--region", help="Region"),
|
|
47
|
+
port: int = typer.Option(8080, "--port", help="Application port"),
|
|
48
|
+
token: Optional[str] = typer.Option(None, "--token", help="Override token"),
|
|
49
|
+
):
|
|
50
|
+
"""Create a new app."""
|
|
51
|
+
c = get_client(token)
|
|
52
|
+
data = sdk_call(c.apps.create, name, plan=plan, region=region, port=port)
|
|
53
|
+
if is_json_mode():
|
|
54
|
+
print_json(data)
|
|
55
|
+
else:
|
|
56
|
+
print(f"App created: {data.get('name', name)}")
|
|
57
|
+
print_object(data, [("id", "ID"), ("name", "Name"), ("status", "Status"),
|
|
58
|
+
("plan", "Plan"), ("default_url", "URL")])
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@app.command("info")
|
|
62
|
+
def info_app(
|
|
63
|
+
app_id: str = typer.Argument(..., help="App ID"),
|
|
64
|
+
token: Optional[str] = typer.Option(None, "--token", help="Override token"),
|
|
65
|
+
):
|
|
66
|
+
"""Show app details."""
|
|
67
|
+
c = get_client(token)
|
|
68
|
+
data = sdk_call(c.apps.get, app_id)
|
|
69
|
+
print_object(data, [
|
|
70
|
+
("id", "ID"), ("name", "Name"), ("status", "Status"), ("plan", "Plan"),
|
|
71
|
+
("region", "Region"), ("port", "Port"), ("default_url", "URL"),
|
|
72
|
+
("image_uri", "Image"), ("created_at", "Created"),
|
|
73
|
+
])
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@app.command("delete")
|
|
77
|
+
def delete_app(
|
|
78
|
+
app_id: str = typer.Argument(..., help="App ID"),
|
|
79
|
+
token: Optional[str] = typer.Option(None, "--token", help="Override token"),
|
|
80
|
+
confirm: bool = typer.Option(False, "--confirm", help="Skip confirmation"),
|
|
81
|
+
):
|
|
82
|
+
"""Delete an app (permanent)."""
|
|
83
|
+
if not confirm:
|
|
84
|
+
print(f"This will permanently delete app {app_id} and all its data.")
|
|
85
|
+
print("Use --confirm to proceed.")
|
|
86
|
+
raise typer.Exit(code=1)
|
|
87
|
+
|
|
88
|
+
c = get_client(token)
|
|
89
|
+
data = sdk_call(c.apps.delete, app_id)
|
|
90
|
+
if is_json_mode():
|
|
91
|
+
print_json(data)
|
|
92
|
+
else:
|
|
93
|
+
print(f"App {app_id}: deleted")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@app.command("deploy")
|
|
97
|
+
def deploy_app(
|
|
98
|
+
app_id: str = typer.Argument(..., help="App ID"),
|
|
99
|
+
image: str = typer.Option(..., "--image", help="Container image URI"),
|
|
100
|
+
token: Optional[str] = typer.Option(None, "--token", help="Override token"),
|
|
101
|
+
):
|
|
102
|
+
"""Deploy an app from a container image."""
|
|
103
|
+
c = get_client(token)
|
|
104
|
+
data = sdk_call(c.apps.deploy, app_id, image)
|
|
105
|
+
if is_json_mode():
|
|
106
|
+
print_json(data)
|
|
107
|
+
else:
|
|
108
|
+
print(f"App {app_id}: deploying image {image}")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@app.command("start")
|
|
112
|
+
def start_app(
|
|
113
|
+
app_id: str = typer.Argument(..., help="App ID"),
|
|
114
|
+
token: Optional[str] = typer.Option(None, "--token", help="Override token"),
|
|
115
|
+
):
|
|
116
|
+
"""Start an app."""
|
|
117
|
+
c = get_client(token)
|
|
118
|
+
data = sdk_call(c.apps.start, app_id)
|
|
119
|
+
if is_json_mode():
|
|
120
|
+
print_json(data)
|
|
121
|
+
else:
|
|
122
|
+
print(f"App {app_id}: {data.get('message', 'starting')}")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@app.command("stop")
|
|
126
|
+
def stop_app(
|
|
127
|
+
app_id: str = typer.Argument(..., help="App ID"),
|
|
128
|
+
token: Optional[str] = typer.Option(None, "--token", help="Override token"),
|
|
129
|
+
):
|
|
130
|
+
"""Stop an app."""
|
|
131
|
+
c = get_client(token)
|
|
132
|
+
data = sdk_call(c.apps.stop, app_id)
|
|
133
|
+
if is_json_mode():
|
|
134
|
+
print_json(data)
|
|
135
|
+
else:
|
|
136
|
+
print(f"App {app_id}: {data.get('message', 'stopping')}")
|