Skip to content

Commit

Permalink
Add Immutable methods (#84)
Browse files Browse the repository at this point in the history
* Add Immutable methods

* Add immutable box to readme

* Fix project arg in test
  • Loading branch information
pederhan authored Jun 17, 2024
1 parent bc39c59 commit 317d0e0
Show file tree
Hide file tree
Showing 6 changed files with 277 additions and 10 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ While the project is still on major version 0, breaking changes may be introduce
### Added

- Permissions method:
- `HarborAsyncClient.get_permissions()`: Get system and project permissions for a user.
- `HarborAsyncClient.get_permissions()`
- Immutable tag rules methods:
- `HarborAsyncClient.get_project_immutable_tag_rules()`
- `HarborAsyncClient.create_project_immutable_tag_rule()`
- `HarborAsyncClient.update_project_immutable_tag_rule()`
- `HarborAsyncClient.enable_project_immutable_tagrule()`
- `HarborAsyncClient.delete_project_immutable_tag_rule()`

### Removed

Expand Down
10 changes: 1 addition & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

-----


Python async client for the Harbor REST API v2.0 based on the official Harbor REST API specification.

**NOTE:** The official Harbor API spec is hand-written, and numerous errors and inconsistencies have been found in it. This library attempts to work around these issues as much as possible, but errors may still occur. If you find any errors, please open an issue.
Expand All @@ -29,7 +28,6 @@ Python async client for the Harbor REST API v2.0 based on the official Harbor RE
pip install harborapi
```


## Documentation

Documentation is available [here](https://unioslo.github.io/harborapi/). The documentation is still a work in progress, and you may have to dig around a bit to find what you're looking for.
Expand All @@ -38,10 +36,8 @@ Creating proper documentation for the Pydantic models is priority number one rig

## Quick Start


### Authentication


```python
from harborapi import HarborAsyncClient

Expand Down Expand Up @@ -83,7 +79,6 @@ async def main() -> None:
asyncio.run(main())
```


### Create a project

```python
Expand Down Expand Up @@ -115,8 +110,6 @@ asyncio.run(main())

All endpoints are documented in the [endpoints documentation](https://unioslo.github.io/harborapi/endpoints/).



## Disclaimer

`harborapi` makes use of code generation for its data models, but it doesn't entirely rely on it like, for example, [githubkit](https://github.com/yanyongyu/githubkit). Thus, while the library is based on the Harbor REST API specification, it is not beholden to it. The official schema contains several inconsistencies and errors, and this package takes steps to rectify some of these locally until they are fixed in the official Harbor API spec.
Expand All @@ -125,7 +118,6 @@ All endpoints are documented in the [endpoints documentation](https://unioslo.gi

To return the raw API responses without validation and type conversion, set `raw=True` when instantiating the client. For more information, check the [documentation](https://unioslo.github.io/harborapi/usage/validation/) on validation.


## Implemented endpoints

<!-- - [ ] Products
Expand All @@ -137,7 +129,7 @@ To return the raw API responses without validation and type conversion, set `raw
- [x] Garbage Collection
- [x] Health
- [x] Icon
- [ ] Immutable
- [x] Immutable
- [x] Label
- [x] Ldap
- [x] OIDC
Expand Down
10 changes: 10 additions & 0 deletions docs/endpoints/immutable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Immutable

::: harborapi.client.HarborAsyncClient
options:
members:
- get_project_immutable_tag_rules
- create_project_immutable_tag_rule
- update_project_immutable_tag_rule
- enable_project_immutable_tagrule
- delete_project_immutable_tag_rule
155 changes: 155 additions & 0 deletions harborapi/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
from .models import GCHistory
from .models import GeneralInfo
from .models import Icon
from .models import ImmutableRule
from .models import IsDefault
from .models import Label
from .models import LdapConf
Expand Down Expand Up @@ -3836,6 +3837,160 @@ async def get_artifact_build_history(
# GET /projects/{project_name}/repositories/{repository_name}/artifacts/{reference}/additions/dependencies

# CATEGORY: immutable
# GET /projects/{project_name_or_id}/immutabletagrules
# List all immutable tag rules of current project
async def get_project_immutable_tag_rules(
self,
project_name_or_id: Union[str, int],
query: Optional[str] = None,
sort: Optional[str] = None,
page: int = 1,
page_size: int = 10,
limit: Optional[int] = None,
) -> List[ImmutableRule]:
"""Get the immutable tag rules for a project.
Parameters
----------
project_name_or_id : Union[str, int]
The name or ID of the project.
String arguments are treated as project names.
Integer arguments are treated as project IDs.
query : Optional[str]
A query string to filter the immutable tag rules
sort : Optional[str]
The sort order of the rules
page : int
The page of results to return
page_size : int
The number of results to return per page
limit : Optional[int]
The maximum number of results to return
Returns
-------
List[ImmutableRule]
The immutable tag rules for the project.
"""
params = get_params(
q=query,
sort=sort,
page=page,
page_size=page_size,
)
headers = get_project_headers(project_name_or_id)
projects = await self.get(
f"/projects/{project_name_or_id}/immutabletagrules",
params=params,
limit=limit,
headers=headers,
)
return self.construct_model(ImmutableRule, projects, is_list=True)

# POST /projects/{project_name_or_id}/immutabletagrules
# Add an immutable tag rule to current project
async def create_project_immutable_tag_rule(
self, project_name_or_id: Union[str, int], rule: ImmutableRule
) -> str:
"""Create an immutable tag rule for a project.
Parameters
----------
project_name_or_id : Union[str, int]
The name or ID of the project.
String arguments are treated as project names.
Integer arguments are treated as project IDs.
rule : ImmutableRule
The immutable tag rule to create.
Returns
-------
str
The location of the created immutable tag rule.
"""
headers = get_project_headers(project_name_or_id)
resp = await self.post(
f"/projects/{project_name_or_id}/immutabletagrules",
json=rule,
headers=headers,
)
return urldecode_header(resp, "Location")

# PUT /projects/{project_name_or_id}/immutabletagrules/{immutable_rule_id}
# Update the immutable tag rule or enable or disable the rule
async def update_project_immutable_tag_rule(
self,
project_name_or_id: Union[str, int],
immutable_rule_id: int,
rule: ImmutableRule,
) -> None:
"""Update an immutable tag rule for a project.
Parameters
----------
project_name_or_id : Union[str, int]
The name or ID of the project.
String arguments are treated as project names.
Integer arguments are treated as project IDs.
immutable_rule_id : int
The ID of the immutable tag rule.
rule : ImmutableRule
The updated immutable tag rule.
"""
headers = get_project_headers(project_name_or_id)
await self.put(
f"/projects/{project_name_or_id}/immutabletagrules/{immutable_rule_id}",
json=rule,
headers=headers,
)

async def enable_project_immutable_tagrule(
self,
project_name_or_id: Union[str, int],
immutable_rule_id: int,
enabled: bool = True,
) -> None:
"""Enable or disable an immutable tag rule for a project.
Parameters
----------
project_name_or_id : Union[str, int]
The name or ID of the project.
String arguments are treated as project names.
Integer arguments are treated as project IDs.
immutable_rule_id : int
The ID of the immutable tag rule.
enabled : bool
Whether to enable or disable the rule.
"""
rule = ImmutableRule(disabled=not enabled)
return await self.update_project_immutable_tag_rule(
project_name_or_id, immutable_rule_id, rule
)

# DELETE /projects/{project_name_or_id}/immutabletagrules/{immutable_rule_id}
# Delete the immutable tag rule.
async def delete_project_immutable_tag_rule(
self,
project_name_or_id: Union[str, int],
immutable_rule_id: int,
) -> None:
"""Delete an immutable tag rule for a project.
Parameters
----------
project_name_or_id : Union[str, int]
The name or ID of the project.
String arguments are treated as project names.
Integer arguments are treated as project IDs.
immutable_rule_id : int
The ID of the immutable tag rule.
"""
headers = get_project_headers(project_name_or_id)
await self.delete(
f"/projects/{project_name_or_id}/immutabletagrules/{immutable_rule_id}",
headers=headers,
)

# CATEGORY: retention

Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ nav:
- endpoints/gc.md
- endpoints/health.md
- endpoints/icon.md
- endpoints/immutable.md
- endpoints/labels.md
- endpoints/ldap.md
- endpoints/oidc.md
Expand Down
103 changes: 103 additions & 0 deletions tests/endpoints/test_immutable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
from __future__ import annotations

from typing import List

import pytest
from hypothesis import HealthCheck
from hypothesis import given
from hypothesis import settings
from hypothesis import strategies as st
from pytest_httpserver import HTTPServer

from harborapi.client import HarborAsyncClient
from harborapi.models import ImmutableRule

from ..utils import json_from_list


@pytest.mark.asyncio
@given(st.lists(st.builds(ImmutableRule)))
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture])
async def test_get_project_immutable_tag_rules_mock(
async_client: HarborAsyncClient,
httpserver: HTTPServer,
rules: List[ImmutableRule],
):
httpserver.expect_oneshot_request(
"/api/v2.0/projects/1234/immutabletagrules", method="GET"
).respond_with_data(
json_from_list(rules), headers={"Content-Type": "application/json"}
)
resp = await async_client.get_project_immutable_tag_rules(1234)
assert resp == rules


@pytest.mark.asyncio
@given(st.builds(ImmutableRule))
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture])
async def test_create_project_immutable_tag_rule_mock(
async_client: HarborAsyncClient,
httpserver: HTTPServer,
rule: ImmutableRule,
):
expect_location = "/api/v2.0/projects/1234/immutabletagrules/1"
httpserver.expect_oneshot_request(
"/api/v2.0/projects/1234/immutabletagrules",
method="POST",
json=rule.model_dump(mode="json", exclude_unset=True),
).respond_with_data(headers={"Location": expect_location})
resp = await async_client.create_project_immutable_tag_rule(1234, rule)
assert resp == expect_location


@pytest.mark.asyncio
@given(st.builds(ImmutableRule))
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture])
async def test_update_project_immutable_tag_rule_mock(
async_client: HarborAsyncClient,
httpserver: HTTPServer,
rule: ImmutableRule,
):
httpserver.expect_oneshot_request(
"/api/v2.0/projects/1234/immutabletagrules/1",
method="PUT",
json=rule.model_dump(mode="json", exclude_unset=True),
headers={"X-Is-Resource-Name": "false"},
).respond_with_data()
async_client.url = httpserver.url_for("/api/v2.0")
await async_client.update_project_immutable_tag_rule(1234, 1, rule)


@pytest.mark.asyncio
@pytest.mark.parametrize(
"enable",
[True, False],
)
async def test_enable_project_immutable_tagrule(
async_client: HarborAsyncClient,
httpserver: HTTPServer,
enable: bool,
):
"""Test updating a rule with only the disabled field set."""
httpserver.expect_oneshot_request(
"/api/v2.0/projects/1234/immutabletagrules/1",
method="PUT",
json={"disabled": not enable},
headers={"X-Is-Resource-Name": "false"},
).respond_with_data()
async_client.url = httpserver.url_for("/api/v2.0")
await async_client.enable_project_immutable_tagrule(1234, 1, enable)


@pytest.mark.asyncio
async def test_delete_project_immutable_tag_rule_mock(
async_client: HarborAsyncClient,
httpserver: HTTPServer,
):
httpserver.expect_oneshot_request(
"/api/v2.0/projects/1234/immutabletagrules/1",
method="DELETE",
headers={"X-Is-Resource-Name": "false"},
).respond_with_data()
async_client.url = httpserver.url_for("/api/v2.0")
await async_client.delete_project_immutable_tag_rule(1234, 1)

0 comments on commit 317d0e0

Please sign in to comment.