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
32 changes: 16 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ cycleops services list
#### Retrieve a service

```
cycleops services retrieve <service_id>
cycleops services retrieve <service_name>|<service_id>
```

#### Create a service
Expand All @@ -71,25 +71,25 @@ cycleops services create --name <service_name> --unit-id <unit_id>
#### Update a service

```
cycleops services update <service_id> --name <service_name> --description <service_description --unit-id <unit_id> --variable <key_1>=<value_1> --variable <key_2>=<value_2> ... --variable <key_n>=<value_n>
cycleops services update <service_name>|<service_id> --name <service_name> --description <service_description --unit-id <unit_id> --variable <key_1>=<value_1> --variable <key_2>=<value_2> ... --variable <key_n>=<value_n>
```

#### Delete a service

```
cycleops services delete <service_id>
cycleops services delete <service_name>|<service_id>
```

#### Create a Container

```
cycleops services create-container <service_id> <container_name> --image <image_name>:<image_tag> --ports <ports>,<ports> --volumes <volume>,<volume>,<volume> --env-file <env_file_path> --command <command>
cycleops services create-container <service_name>|<service_id> <container_name> --image <image_name>:<image_tag> --ports <ports>,<ports> --volumes <volume>,<volume>,<volume> --env-file <env_file_path> --command <command>
```

#### Update a Container

```
cycleops services update-container <service_id> <container_name> --name <new_container_name> --image <image_name>:<image_tag> --ports <ports>,<ports> --volumes <volume>,<volume>,<volume> --env-file <env_file_path> --command <command>
cycleops services update-container <service_name>|<service_id> <container_name> --name <new_container_name> --image <image_name>:<image_tag> --ports <ports>,<ports> --volumes <volume>,<volume>,<volume> --env-file <env_file_path> --command <command>
```

### Stacks
Expand All @@ -103,7 +103,7 @@ cycleops stacks list
#### Retrieve a stack

```
cycleops stacks retrieve <stack_id>
cycleops stacks retrieve <stack_name>|<stack_id>
```

#### Create a stack
Expand All @@ -115,13 +115,13 @@ cycleops stacks create --name <stack_name>
#### Update a stack

```
cycleops stacks update <stack_id> --name <stack_name> --description <stack_description> --unit-id <unit_id> ... --unit-id <unit_id>
cycleops stacks update <stack_name>|<stack_id> --name <stack_name> --description <stack_description> --unit-id <unit_id> ... --unit-id <unit_id>
```

#### Delete a stack

```
cycleops stacks delete <stack_id>
cycleops stacks delete <stack_name>|<stack_id>
```

### Setups
Expand All @@ -135,7 +135,7 @@ cycleops setups list
#### Retrieve a setup

```
cycleops setups retrieve <setup_id>
cycleops setups retrieve <setup_name>|<setup_id>
```

#### Create a setup
Expand All @@ -147,19 +147,19 @@ cycleops setups create --name <setup_name>
#### Update a setup

```
cycleops setups update <setup_id> --name <setup_name> --stack-id <stack_id> --environment-id <environment_id> --host-id <host_id> ... --host-id <host_id> --hostgroup-id <hostgroup_id> ... --hostgroup-id <hostgroup_id> --service-id <service_id> ... --service-id <service_id>
cycleops setups update <setup_name>|<setup_id> --name <setup_name> --stack-id <stack_id> --environment-id <environment_id> --host-id <host_id> ... --host-id <host_id> --hostgroup-id <hostgroup_id> ... --hostgroup-id <hostgroup_id> --service-id <service_id> ... --service-id <service_id>
```

#### Delete a setup

```
cycleops setups delete <setup_id>
cycleops setups delete <setup_name>|<setup_id>
```

#### Deploy a setup

```
cycleops setups deploy <setup_id>
cycleops setups deploy <setup_name>|<setup_id>
```

### GitHub Actions
Expand All @@ -179,18 +179,18 @@ You can use Cycleops with your GitHub Actions to deploy a setup right from GitHu
- name: Install Cycleops
run: pip install cycleops
- name: Update Cycleops services
run: cycleops services update-container <service_id> <container_name> --image <image_name>:<image_tag>
run: cycleops services update-container <service_name> <container_name> --image <image_name>:<image_tag>
- name: Deploy Cycleops setups
run: cycleops setups deploy <setup_id>
run: cycleops setups deploy <setup_name>
```

## Example

You can update a service's image and deploy a setup as follows:

```console
$ cycleops services update 17 --variable containers.0.image=nginx:1.23
$ cycleops setups deploy 44
$ cycleops services update default-container-ngnix --variable containers.0.image=nginx:1.23
$ cycleops setups deploy default-container-ngnix
```

## License
Expand Down
34 changes: 27 additions & 7 deletions cycleops/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,19 @@ def __init__(
self.api_key: str = sec.load("CYCLEOPS_API_KEY", api_key)

def _request(
self, method: str, endpoint: str, payload: Optional[Dict[str, Any]] = None
self,
method: str,
endpoint: str,
payload: Optional[Dict[str, Any]] = None,
params: Optional[Dict[str, Any]] = None,
) -> Optional[Dict[str, Any]]:
url: str = f"{self.base_url}/{endpoint}"
response: Response = requests.request(
method,
url,
json=payload,
auth=CycleopsAuthentication(self.api_key),
params=params,
)
return self._handle_response(response)

Expand Down Expand Up @@ -75,8 +80,13 @@ class ServiceClient(SubClient):
def list(self) -> Optional[Dict[str, Any]]:
return self.client._request("GET", f"services")

def retrieve(self, service_id: int) -> Optional[Dict[str, Any]]:
return self.client._request("GET", f"services/{service_id}")
def retrieve(
self, service_id: Optional[int] = None, params: Optional[Dict[str, Any]] = None
) -> Optional[Dict[str, Any]]:
if service_id:
return self.client._request("GET", f"services/{service_id}")

return self.client._request("GET", f"services", params=params)

def create(self, **kwargs: Any) -> Optional[Dict[str, Any]]:
payload: Dict[str, Any] = {k: v for (k, v) in kwargs.items() if v}
Expand Down Expand Up @@ -114,8 +124,13 @@ class SetupClient(SubClient):
def list(self) -> Optional[Dict[str, Any]]:
return self.client._request("GET", f"setups")

def retrieve(self, setup_id: int) -> Optional[Dict[str, Any]]:
return self.client._request("GET", f"setups/{setup_id}")
def retrieve(
self, setup_id: Optional[int] = None, params: Optional[Dict[str, Any]] = None
) -> Optional[Dict[str, Any]]:
if setup_id:
return self.client._request("GET", f"setups/{setup_id}")

return self.client._request("GET", f"setups", params=params)

def create(self, **kwargs: Any) -> Optional[Dict[str, Any]]:
payload: Dict[str, Any] = {k: v for (k, v) in kwargs.items() if v}
Expand Down Expand Up @@ -155,8 +170,13 @@ class StackClient(SubClient):
def list(self) -> Optional[Dict[str, Any]]:
return self.client._request("GET", f"stacks")

def retrieve(self, stack_id: int) -> Optional[Dict[str, Any]]:
return self.client._request("GET", f"stacks/{stack_id}")
def retrieve(
self, stack_id: Optional[int] = None, params: Optional[Dict[str, Any]] = None
) -> Optional[Dict[str, Any]]:
if stack_id:
return self.client._request("GET", f"stacks/{stack_id}")

return self.client._request("GET", f"stacks", params=params)

def create(self, **kwargs: Any) -> Optional[Dict[str, Any]]:
payload: Dict[str, Any] = {k: v for (k, v) in kwargs.items() if v}
Expand Down
73 changes: 44 additions & 29 deletions cycleops/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,16 @@ def list() -> None:

@app.command()
def retrieve(
service_id: int = typer.Argument(..., help="The ID of the service."),
service_identifier: str = typer.Argument(
..., help="The ID or name of the service. Names take precedence."
),
) -> None:
"""
Retrieve the service specified by the given ID.
"""

try:
service = service_client.retrieve(service_id)

if not service:
raise NotFound("No service with such ID exists")
service = get_service(service_identifier)

print(service)
except Exception as error:
Expand Down Expand Up @@ -87,7 +86,9 @@ def create(

@app.command()
def update(
service_id: int = typer.Argument(..., help="The ID of the service."),
service_identifier: str = typer.Argument(
..., help="The ID or name of the service. Names take precedence."
),
name: Optional[str] = typer.Option(
None,
help="The name of the service.",
Expand All @@ -111,39 +112,40 @@ def update(
"""

try:
service = service_client.retrieve(service_id)

if not service:
raise ValueError("Invalid resource: Service")
service = get_service(service_identifier)

if variables:
variables = dict_from_variables(variables, service["variables"])

service_client.update(
service_id,
service["id"],
name=name,
description=description,
unit=unit_id,
variables=variables,
)

display_success_message(f"Service {service_id} has been updated")
display_success_message(f"Service {service_identifier} has been updated")
except Exception as error:
display_error_message(error)
raise typer.Abort()


@app.command()
def delete(
service_id: int = typer.Argument(..., help="The ID of the service."),
service_identifier: str = typer.Argument(
..., help="The ID or name of the service. Names take precedence."
),
) -> None:
"""
Delete the service specified by the given ID.
"""

try:
service_client.delete(service_id)
display_success_message(f"Service {service_id} has been deleted")
service = get_service(service_identifier)

service_client.delete(service["id"])
display_success_message(f"Service {service_identifier} has been deleted")
except Exception as error:
display_error_message(error)
raise typer.Abort()
Expand Down Expand Up @@ -238,7 +240,9 @@ def parse_if_bool(value: str) -> Union[str, bool]:

@app.command()
def create_container(
service_id: int = typer.Argument(..., help="The ID of the service."),
service_identifier: str = typer.Argument(
..., help="The ID or name of the service. Names take precedence."
),
container_name: str = typer.Option(
..., help="The name of the container to be created."
),
Expand Down Expand Up @@ -268,10 +272,7 @@ def create_container(
"""

try:
service = service_client.retrieve(service_id)

if not service:
raise ValueError(f"Service {service_id} not found")
service = get_service(service_identifier)

image_name = None
image_tag = None
Expand Down Expand Up @@ -314,12 +315,12 @@ def create_container(
)

service_client.update(
service_id,
service["id"],
variables=service["variables"],
)

display_success_message(
f"Container {container_name} in Service {service_id} has been created"
f"Container {container_name} in Service {service_identifier} has been created"
)
except Exception as error:
display_error_message(error)
Expand All @@ -328,7 +329,9 @@ def create_container(

@app.command()
def update_container(
service_id: int = typer.Argument(..., help="The ID of the service."),
service_identifier: str = typer.Argument(
..., help="The ID or name of the service. Names take precedence."
),
container_name: str = typer.Argument(
..., help="The name of the container to be updated."
),
Expand Down Expand Up @@ -362,10 +365,7 @@ def update_container(
"""

try:
service = service_client.retrieve(service_id)

if not service:
raise ValueError(f"Service {service_id} not found")
service = get_service(service_identifier)

container_index = None
for index, container in enumerate(service["variables"]["containers"]):
Expand Down Expand Up @@ -421,13 +421,28 @@ def update_container(
service["variables"]["containers"][container_index][key] = value

service_client.update(
service_id,
service["id"],
variables=service["variables"],
)

display_success_message(
f"Container {container_name} in Service {service_id} has been updated"
f"Container {container_name} in Service {service_identifier} has been updated"
)
except Exception as error:
display_error_message(error)
raise typer.Abort()


def get_service(service_identifier: str) -> Optional[Dict[str, Any]]:
"""
Retrieves a Service with either a name or ID. Names take precedence.
"""

service = service_client.retrieve(params={"name": service_identifier})

if len(service) == 1:
return service[0]

service = service_client.retrieve(service_identifier)

return service
Loading