Skip to content

Commit 337af22

Browse files
committed
feat(cli): Add operations commands
Add commands for working with cloud operations.
1 parent 50dfc20 commit 337af22

File tree

20 files changed

+370
-35
lines changed

20 files changed

+370
-35
lines changed

apps/cli/bin/celest.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:celest_cli/celest_cli.dart';
22
import 'package:celest_cli/src/commands/auth/auth_command.dart';
3+
import 'package:celest_cli/src/commands/cloud/operations/operations_command.dart';
34
import 'package:celest_cli/src/commands/cloud/organizations/organizations_command.dart';
45
import 'package:celest_cli/src/commands/cloud/project_environments/project_environments_command.dart';
56
import 'package:celest_cli/src/commands/cloud/projects/projects_command.dart';
@@ -32,7 +33,8 @@ void main(List<String> args) async {
3233
cli
3334
..addCommand(OrganizationsCommand())
3435
..addCommand(ProjectsCommand())
35-
..addCommand(ProjectEnvironmentsCommand());
36+
..addCommand(ProjectEnvironmentsCommand())
37+
..addCommand(OperationsCommand());
3638

3739
await cli.run(args);
3840
}

apps/cli/lib/src/commands/cloud/cloud_command.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,8 @@ abstract base class CloudDeleteCommand<R extends GeneratedMessage>
484484

485485
extension type CloudCommandOptions(ArgResults argResults)
486486
implements ArgResults {
487+
String? get maybeResourceId => rest.elementAtOrNull(0);
488+
487489
String get resourceId {
488490
final id = rest.elementAtOrNull(0);
489491
if (id == null) {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import 'package:celest_cli/src/commands/cloud/cloud_command.dart';
2+
import 'package:celest_cli/src/context.dart' show cloud;
3+
import 'package:celest_cloud/celest_cloud.dart';
4+
5+
final class GetOperationCommand extends CloudGetCommand<Operation> {
6+
@override
7+
String get description => 'Gets an operation.';
8+
9+
@override
10+
String get name => 'get';
11+
12+
@override
13+
String get resourceType => 'Operation';
14+
15+
@override
16+
Operation createEmptyResource() => Operation();
17+
18+
@override
19+
Future<Operation?> callService() async {
20+
return cloud.operations.get(options.resourceId);
21+
}
22+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import 'package:celest_cli/src/commands/cloud/cloud_command.dart';
2+
import 'package:celest_cli/src/context.dart' show cloud;
3+
import 'package:celest_cloud/celest_cloud.dart';
4+
import 'package:celest_cloud_core/celest_cloud_core.dart';
5+
6+
final class ListOperationsCommand extends CloudListCommand<Operation> {
7+
@override
8+
String get description => 'Lists operations.';
9+
10+
@override
11+
String get name => 'list';
12+
13+
@override
14+
String get resourceType => 'Operation';
15+
16+
@override
17+
String? get parentResourceType => null;
18+
19+
@override
20+
Operation createEmptyResource() => Operation();
21+
22+
@override
23+
Future<CloudListResult<Operation>> callService() async {
24+
var filter = options.filter;
25+
if (options.maybeResourceId case final resourceId?) {
26+
final resourceName = ResourceName.parse(resourceId);
27+
final resourceFilter = 'resource_type = "${resourceName.uid.type}" AND '
28+
'resource_id = "${resourceName.uid.id}"';
29+
if (filter == null || filter.isEmpty) {
30+
filter = resourceFilter;
31+
} else {
32+
filter = '$filter AND $resourceFilter';
33+
}
34+
}
35+
final result = await cloud.operations.list(
36+
filter: filter,
37+
pageSize: options.pageSize,
38+
);
39+
return (
40+
items: result.operations,
41+
nextPageToken: result.hasNextPageToken() ? result.nextPageToken : null,
42+
);
43+
}
44+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import 'package:celest_cli/src/commands/celest_command.dart';
2+
import 'package:celest_cli/src/commands/cloud/operations/get_operation_command.dart';
3+
import 'package:celest_cli/src/commands/cloud/operations/list_operations_command.dart';
4+
5+
final class OperationsCommand extends CelestCommand {
6+
OperationsCommand() {
7+
addSubcommand(GetOperationCommand());
8+
addSubcommand(ListOperationsCommand());
9+
}
10+
11+
@override
12+
String get description => 'Manage Celest Cloud operations.';
13+
14+
@override
15+
String get name => 'operations';
16+
17+
@override
18+
String get category => 'Cloud';
19+
20+
@override
21+
bool get hidden => true;
22+
}

packages/celest_cloud/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
# 0.1.8
1+
# 0.1.8-wip
22

33
- feat: Add `ProjectAsset_Type.DART_EXECUTABLE`
4+
- fix: Incorrect route for `Operations.listOperations`
45

56
# 0.1.7
67

packages/celest_cloud/lib/src/cloud/operations/operations_protocol.http.dart

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,14 @@ final class OperationsProtocolHttp
6565

6666
@override
6767
Future<ListOperationsResponse> list(ListOperationsRequest request) async {
68-
if (request.name == '') {
69-
throw ArgumentError.value(request.name, 'name', 'must not be empty');
68+
if (request.name.isNotEmpty) {
69+
throw ArgumentError.value(
70+
request.name,
71+
'name',
72+
'only empty name is allowed',
73+
);
7074
}
71-
final path = '/v1alpha1/${request.name}/operations';
75+
const path = '/v1alpha1/operations';
7276
final url = _baseUri.replace(
7377
path: path,
7478
queryParameters: {

packages/celest_cloud/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: celest_cloud
22
description: API contracts and Dart clients for the Celest Cloud platform.
3-
version: 0.1.8
3+
version: 0.1.8-wip
44
repository: https://github.com/celest-dev/celest
55

66
environment:

services/celest_cloud_core/lib/celest_cloud_core.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@ library;
44
export 'src/model/filter.dart';
55
export 'src/model/order_by.dart';
66
export 'src/model/page_token.dart';
7-
export 'src/model/resource.dart';
7+
export 'src/model/resource_name.dart';
8+
export 'src/model/resource_table.dart';
9+
export 'src/model/resource_type.dart';
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import 'package:cedar/ast.dart';
2+
import 'package:celest_cloud_core/src/model/resource_type.dart';
3+
4+
typedef _ResourceNameInfo = ({
5+
ResourceType type,
6+
EntityUid uid,
7+
List<EntityUid> parents,
8+
});
9+
10+
/// A resource name in Celest Cloud.
11+
///
12+
/// Example: `organizations/123/projects/456/environments/789`
13+
extension type ResourceName(_ResourceNameInfo _info) {
14+
/// Parses a resource name string into a [ResourceName] object.
15+
factory ResourceName.parse(String name) {
16+
final segments = name.split('/').reversed.toList();
17+
if (segments.length < 2 || segments.length % 2 != 0) {
18+
throw ArgumentError('Invalid resource name: $name');
19+
}
20+
21+
(ResourceType, String)? uid;
22+
final parents = <EntityUid>[];
23+
for (var i = 0; i < segments.length; i += 2) {
24+
final type = segments[i + 1];
25+
final id = segments[i];
26+
if (type.isEmpty || id.isEmpty) {
27+
throw ArgumentError('Invalid resource name: $name');
28+
}
29+
final resourceType = ResourceType.fromPatternType(type);
30+
if (uid == null) {
31+
uid = (resourceType, id);
32+
} else {
33+
parents.add(resourceType.uid(id));
34+
}
35+
}
36+
37+
final (resourceType, id) = uid!;
38+
return ResourceName((
39+
type: resourceType,
40+
uid: resourceType.uid(id),
41+
parents: parents,
42+
));
43+
}
44+
45+
/// A map of resource pattern types to their Cedar entity types.
46+
static const Map<String, EntityTypeName> entityTypes = {
47+
'operations': EntityTypeName('Celest::Operation'),
48+
'users': EntityTypeName('Celest::User'),
49+
'sessions': EntityTypeName('Celest::Session'),
50+
'projects': EntityTypeName('Celest::Project'),
51+
'organizations': EntityTypeName('Celest::Organization'),
52+
'environments': EntityTypeName('Celest::Project::Environment'),
53+
};
54+
55+
/// The resource type for this resource.
56+
ResourceType get type => _info.type;
57+
58+
/// The unique identifier for this resource.
59+
EntityUid get uid => _info.uid;
60+
61+
/// The list of parent resources for this resource.
62+
List<EntityUid> get parents => _info.parents;
63+
}

0 commit comments

Comments
 (0)