Skip to content

harborapi.ext.report

harborapi.ext.report defines classes and functions for aggregating the data of multiple artifacts, including their repositories and vulnerability reports.

ArtifactCVSS dataclass

Source code in harborapi/ext/report.py
@dataclass
class ArtifactCVSS:
    cvss: CVSSData
    artifact: ArtifactInfo

    @classmethod
    def from_artifactinfo_cvss(cls, artifact: ArtifactInfo) -> "ArtifactCVSS":
        """Create a CVSSData instance from an ArtifactInfo."""
        return cls(
            cvss=artifact.cvss,
            artifact=artifact,
        )

from_artifactinfo_cvss(artifact) classmethod

Create a CVSSData instance from an ArtifactInfo.

Source code in harborapi/ext/report.py
@classmethod
def from_artifactinfo_cvss(cls, artifact: ArtifactInfo) -> "ArtifactCVSS":
    """Create a CVSSData instance from an ArtifactInfo."""
    return cls(
        cvss=artifact.cvss,
        artifact=artifact,
    )

ArtifactReport

Bases: BaseModel

Aggregation of artifacts and their vulnerabilities.

Source code in harborapi/ext/report.py
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
class ArtifactReport(BaseModel):
    """Aggregation of artifacts and their vulnerabilities."""

    artifacts: List[ArtifactInfo] = []

    model_config = ConfigDict(ignored_types=(cached_property,))

    @field_validator("artifacts", mode="before")
    def _none_artifacts_is_empty_list(cls, v: Any) -> Any:
        """If `artifacts` is None, set it to an empty list."""
        if v is None:
            return []
        return v

    @classmethod
    def from_artifacts(cls, artifacts: Iterable[ArtifactInfo]) -> "ArtifactReport":
        """Create an ArtifactReport from an iterable of ArtifactInfo instances.
        Does not validate the artifacts for faster construction.

        !!! warning
            Only use this with artifacts that have already been validated,
            (e.g. from an existing ArtifactReport).

        Parameters
        ----------
        artifacts : Iterable[ArtifactInfo]
            The artifacts to include in the report.

        Returns
        -------
        ArtifactReport
            A report with the given artifacts.
        """
        return cls.model_construct(artifacts=artifacts)

    def __bool__(self) -> bool:
        return bool(self.artifacts)

    def __len__(self) -> int:
        return len(self.artifacts)

    @property
    def is_aggregate(self) -> bool:
        return len(self.artifacts) > 1

    @cached_property
    def cvss(self) -> CVSSData:
        """Get an aggregate of CVSS data for the artifacts in this report."""
        return CVSSData.from_report(self)

    # TODO: The methods that return Iterable[Vulnerability] are inconsistent
    # with the other methods that return ArtifactReport. We should probably
    # change them to return ArtifactReport or Iterable[ArtifactInfo],
    # or change their names to reflect that they return invididual vulnerabilities.

    @property
    def fixable(self) -> Iterable[Vulnerability]:
        """Get all fixable vulnerabilities."""
        for a in self.artifacts:
            for v in a.report.fixable:
                yield Vulnerability(v, a)

    @property
    def unfixable(self) -> Iterable[Vulnerability]:
        """Get all fixable vulnerabilities."""
        for a in self.artifacts:
            for v in a.report.unfixable:
                yield Vulnerability(v, a)

    @property
    def critical(self) -> Iterable[Vulnerability]:
        """Get all critical vulnerabilities.

        Yields
        ------
        Vulnerability
            A vulnerability with its artifact.
        """
        yield from self.vulnerabilities_by_severity(Severity.critical)

    @property
    def high(self) -> Iterable[Vulnerability]:
        """Get all high vulnerabilities.

        Yields
        ------
        Vulnerability
            A vulnerability with its artifact.
        """
        yield from self.vulnerabilities_by_severity(Severity.high)

    @property
    def medium(self) -> Iterable[Vulnerability]:
        """Get all medium vulnerabilities.

        Yields
        ------
        Vulnerability
            A vulnerability with its artifact.
        """
        yield from self.vulnerabilities_by_severity(Severity.medium)

    @property
    def low(self) -> Iterable[Vulnerability]:
        """Get all low vulnerabilities.

        Yields
        ------
        Vulnerability
            A vulnerability with its artifact.
        """
        yield from self.vulnerabilities_by_severity(Severity.low)

    @property
    def distribution(self) -> "Counter[Severity]":
        """Get the distribution of severities from the vulnerabilities of all artifacts.

        Example
        -------
        ```py
        >>> report.distribution
        Counter({Severity.high: 2, Severity.medium: 1})
        ```

        Returns
        -------
        Counter[Severity]
            A counter of the severities.
        """
        dist = Counter()  # type: Counter[Severity]
        for artifact in self.artifacts:
            a_dist = artifact.report.distribution
            dist.update(a_dist)
        return dist

    def vulnerabilities_by_severity(
        self, severity: Severity
    ) -> Iterable[Vulnerability]:
        for a in self.artifacts:
            for v in a.report.vulnerabilities_by_severity(severity):
                yield Vulnerability(v, a)

    def has_cve(self, cve_id: str) -> bool:
        """Check if any of the artifacts has the given CVE.

        Parameters
        ----------
        cve_id : str
            The CVE ID, e.g. CVE-2019-1234.

        Returns
        -------
        bool
            True if any of the artifacts has the given CVE, False otherwise.
        """
        return any(a.has_cve(cve_id) for a in self.artifacts)

    def with_cve(self, cve_id: str) -> "ArtifactReport":
        """Get all artifacts that have the given CVE.

        Parameters
        ----------
        cve_id : str
            The CVE ID, e.g. CVE-2019-1234.

        Returns
        -------
        ArtifactReport
            A report with all artifacts that are affected by the given CVE.
        """
        return ArtifactReport.from_artifacts(
            [a for a in self.artifacts if a.has_cve(cve_id)]
        )

    def has_description(self, description: str, case_sensitive: bool = False) -> bool:
        """Check if any of the artifacts have a vulnerability with a description
        that contains the given string.

        Parameters
        ----------
        description : str
            The description to search for.
        case_sensitive : bool
            Whether the search should be case sensitive, by default False.

        Returns
        -------
        bool
            True if any of the artifacts has the given description, False otherwise.
        """
        return any(
            a.has_description(description, case_sensitive=case_sensitive)
            for a in self.artifacts
        )

    def with_description(
        self, description: str, case_sensitive: bool = False
    ) -> "ArtifactReport":
        """Get all artifacts that have a vulnerability containing the given string.

        Parameters
        ----------
        description : str
            The string to search for in vulnerability descriptions.
        case_sensitive : bool
            Case sensitive matching

        Returns
        -------
        ArtifactReport
            A report with all artifacts that have a vulnerability containing the given
            string.
        """
        return ArtifactReport.from_artifacts(
            [
                a
                for a in self.artifacts
                if a.has_description(description, case_sensitive)
            ]
        )

    def has_package(
        self,
        package: str,
        case_sensitive: bool = False,
        min_version: Optional[VersionType] = None,
        max_version: Optional[VersionType] = None,
    ) -> bool:
        """Check if any of the artifacts has the given package.

        Parameters
        ----------
        package : str
            The package name to search for.
        case_sensitive : bool
            Whether the search should be case sensitive, by default False.
        min_version : Optional[VersionType]
            The minimum version of the package to search for, by default None.
        max_version : Optional[VersionType]
            The maximum version of the package to search for, by default None.

        Returns
        -------
        bool
            True if any of the artifacts has the given package, False otherwise.
        """
        return any(
            a.has_package(
                package,
                case_sensitive=case_sensitive,
                min_version=min_version,
                max_version=max_version,
            )
            for a in self.artifacts
        )

    def with_package(
        self,
        package: str,
        case_sensitive: bool = False,
        min_version: Optional[VersionType] = None,
        max_version: Optional[VersionType] = None,
    ) -> "ArtifactReport":
        """Get all artifacts that have a vulnerability affecting the given package.

        Parameters
        ----------
        package : str
            The name of the package to search for.
            Supports regular expressions.
        case_sensitive : bool
            Case sensitive matching
        min_version : Optional[VersionType]
            The minimum version of the package to search for, by default None.
        max_version : Optional[VersionType]
            The maximum version of the package to search for, by default None.

        Returns
        -------
        ArtifactReport
            An artifact report with all artifacts that have a vulnerability affecting
            the given package.
        """
        return ArtifactReport.from_artifacts(
            [
                a
                for a in self.artifacts
                if a.has_package(
                    package,
                    case_sensitive,
                    min_version=min_version,
                    max_version=max_version,
                )
            ],
        )

    def has_severity(self, severity: Severity) -> bool:
        """Check if any of the artifacts has a vulnerability with the given severity.

        Parameters
        ----------
        severity : Severity
            The severity to search for.

        Returns
        -------
        bool
            True if any of the artifacts has the given severity, False otherwise.
        """
        return bool(self.with_severity(severity).artifacts)

    def with_severity(self, severity: Severity) -> "ArtifactReport":
        """Get all artifacts that have a report with the given severity.

        Parameters
        ----------
        severity : Severity
            The severity to search for.

        Returns
        -------
        ArtifactReport
            An artifact report with all artifacts that have a vulnerability with the
            given severity.
        """
        return ArtifactReport.from_artifacts(
            [a for a in self.artifacts if a.report.severity == severity]
        )

    def has_repository(self, repository: str, case_sensitive: bool = False) -> bool:
        """Check if any of the artifacts belong to the given repository.

        Parameters
        ----------
        repository : str
            The repository name to search for.
            Supports regular expressons.
        case_sensitive : bool, optional
            Case sensitive search, by default False

        Returns
        -------
        bool
            Whether any of the artifacts belong to the given repository.
        """
        return bool(self.with_repository(repository, case_sensitive).artifacts)

    def with_repository(
        self, repositories: Union[str, List[str]], case_sensitive: bool = False
    ) -> "ArtifactReport":
        """Return a new report with all artifacts belonging to one or more repositories.

        Parameters
        ----------
        repositories : Union[str, List[str]]
            A repository or a list of repositories to filter for.
            Supports regular expressions.
        case_sensitive : bool
            Case sensitive repository name matching, by default False

        Returns
        -------
        ArtifactReport
            A new ArtifactReport where all artifacts belong to one of the given
            repositories.
        """
        # Docker doesn't allow upper-case letters in repository names, but
        # I could not find any documentation on whether Harbor allows it.
        # So we'll just assume that it does. Worst case scenario, the `case_sensitive`
        # parameter will be redundant, but that's fine just to ensure compatibility.

        if isinstance(repositories, str):
            repositories = [repositories]
        elif not isinstance(repositories, list) or not all(
            isinstance(r, str) for r in repositories
        ):
            raise TypeError(
                "repositories must be either a string or a list of strings"
            )  # pragma: no cover

        # Make regex pattern for each repository
        # Our cache function only accepts string arguments, but it's fine to not
        # use it here, since this method is not called nearly as often as the underlying
        # `has_*` methods on the ArtifactInfo objects.
        pattern = re.compile(
            "|".join(repositories), flags=re.IGNORECASE if not case_sensitive else 0
        )
        return ArtifactReport.from_artifacts(
            [
                a
                for a in self.artifacts
                if a.repository.name and pattern.match(a.repository.name)
            ]
        )

    def has_tag(self, tag: str) -> bool:
        """Check if any of the artifacts has the given tag.

        Parameters
        ----------
        tag : str
            The tag to search for.

        Returns
        -------
        bool
            True if any of the artifacts has the given tag, False otherwise.
        """
        return any(a.has_tag(tag) for a in self.artifacts)

    def with_tag(self, tag: str) -> "ArtifactReport":
        """Return a new report with all artifacts having the given tag.

        Parameters
        ----------
        tag : str
            The tag to filter for.

        Returns
        -------
        ArtifactReport
            A new ArtifactReport where all artifacts have the given tag.
        """
        return ArtifactReport.from_artifacts(
            [a for a in self.artifacts if a.has_tag(tag)]
        )

cvss: CVSSData cached property

Get an aggregate of CVSS data for the artifacts in this report.

fixable: Iterable[Vulnerability] property

Get all fixable vulnerabilities.

unfixable: Iterable[Vulnerability] property

Get all fixable vulnerabilities.

critical: Iterable[Vulnerability] property

Get all critical vulnerabilities.

Yields:

Type Description
Vulnerability

A vulnerability with its artifact.

high: Iterable[Vulnerability] property

Get all high vulnerabilities.

Yields:

Type Description
Vulnerability

A vulnerability with its artifact.

medium: Iterable[Vulnerability] property

Get all medium vulnerabilities.

Yields:

Type Description
Vulnerability

A vulnerability with its artifact.

low: Iterable[Vulnerability] property

Get all low vulnerabilities.

Yields:

Type Description
Vulnerability

A vulnerability with its artifact.

distribution: 'Counter[Severity]' property

Get the distribution of severities from the vulnerabilities of all artifacts.

Example
>>> report.distribution
Counter({Severity.high: 2, Severity.medium: 1})

Returns:

Type Description
Counter[Severity]

A counter of the severities.

from_artifacts(artifacts) classmethod

Create an ArtifactReport from an iterable of ArtifactInfo instances. Does not validate the artifacts for faster construction.

Warning

Only use this with artifacts that have already been validated, (e.g. from an existing ArtifactReport).

Parameters:

Name Type Description Default
artifacts Iterable[ArtifactInfo]

The artifacts to include in the report.

required

Returns:

Type Description
ArtifactReport

A report with the given artifacts.

Source code in harborapi/ext/report.py
@classmethod
def from_artifacts(cls, artifacts: Iterable[ArtifactInfo]) -> "ArtifactReport":
    """Create an ArtifactReport from an iterable of ArtifactInfo instances.
    Does not validate the artifacts for faster construction.

    !!! warning
        Only use this with artifacts that have already been validated,
        (e.g. from an existing ArtifactReport).

    Parameters
    ----------
    artifacts : Iterable[ArtifactInfo]
        The artifacts to include in the report.

    Returns
    -------
    ArtifactReport
        A report with the given artifacts.
    """
    return cls.model_construct(artifacts=artifacts)

has_cve(cve_id)

Check if any of the artifacts has the given CVE.

Parameters:

Name Type Description Default
cve_id str

The CVE ID, e.g. CVE-2019-1234.

required

Returns:

Type Description
bool

True if any of the artifacts has the given CVE, False otherwise.

Source code in harborapi/ext/report.py
def has_cve(self, cve_id: str) -> bool:
    """Check if any of the artifacts has the given CVE.

    Parameters
    ----------
    cve_id : str
        The CVE ID, e.g. CVE-2019-1234.

    Returns
    -------
    bool
        True if any of the artifacts has the given CVE, False otherwise.
    """
    return any(a.has_cve(cve_id) for a in self.artifacts)

with_cve(cve_id)

Get all artifacts that have the given CVE.

Parameters:

Name Type Description Default
cve_id str

The CVE ID, e.g. CVE-2019-1234.

required

Returns:

Type Description
ArtifactReport

A report with all artifacts that are affected by the given CVE.

Source code in harborapi/ext/report.py
def with_cve(self, cve_id: str) -> "ArtifactReport":
    """Get all artifacts that have the given CVE.

    Parameters
    ----------
    cve_id : str
        The CVE ID, e.g. CVE-2019-1234.

    Returns
    -------
    ArtifactReport
        A report with all artifacts that are affected by the given CVE.
    """
    return ArtifactReport.from_artifacts(
        [a for a in self.artifacts if a.has_cve(cve_id)]
    )

has_description(description, case_sensitive=False)

Check if any of the artifacts have a vulnerability with a description that contains the given string.

Parameters:

Name Type Description Default
description str

The description to search for.

required
case_sensitive bool

Whether the search should be case sensitive, by default False.

False

Returns:

Type Description
bool

True if any of the artifacts has the given description, False otherwise.

Source code in harborapi/ext/report.py
def has_description(self, description: str, case_sensitive: bool = False) -> bool:
    """Check if any of the artifacts have a vulnerability with a description
    that contains the given string.

    Parameters
    ----------
    description : str
        The description to search for.
    case_sensitive : bool
        Whether the search should be case sensitive, by default False.

    Returns
    -------
    bool
        True if any of the artifacts has the given description, False otherwise.
    """
    return any(
        a.has_description(description, case_sensitive=case_sensitive)
        for a in self.artifacts
    )

with_description(description, case_sensitive=False)

Get all artifacts that have a vulnerability containing the given string.

Parameters:

Name Type Description Default
description str

The string to search for in vulnerability descriptions.

required
case_sensitive bool

Case sensitive matching

False

Returns:

Type Description
ArtifactReport

A report with all artifacts that have a vulnerability containing the given string.

Source code in harborapi/ext/report.py
def with_description(
    self, description: str, case_sensitive: bool = False
) -> "ArtifactReport":
    """Get all artifacts that have a vulnerability containing the given string.

    Parameters
    ----------
    description : str
        The string to search for in vulnerability descriptions.
    case_sensitive : bool
        Case sensitive matching

    Returns
    -------
    ArtifactReport
        A report with all artifacts that have a vulnerability containing the given
        string.
    """
    return ArtifactReport.from_artifacts(
        [
            a
            for a in self.artifacts
            if a.has_description(description, case_sensitive)
        ]
    )

has_package(package, case_sensitive=False, min_version=None, max_version=None)

Check if any of the artifacts has the given package.

Parameters:

Name Type Description Default
package str

The package name to search for.

required
case_sensitive bool

Whether the search should be case sensitive, by default False.

False
min_version Optional[VersionType]

The minimum version of the package to search for, by default None.

None
max_version Optional[VersionType]

The maximum version of the package to search for, by default None.

None

Returns:

Type Description
bool

True if any of the artifacts has the given package, False otherwise.

Source code in harborapi/ext/report.py
def has_package(
    self,
    package: str,
    case_sensitive: bool = False,
    min_version: Optional[VersionType] = None,
    max_version: Optional[VersionType] = None,
) -> bool:
    """Check if any of the artifacts has the given package.

    Parameters
    ----------
    package : str
        The package name to search for.
    case_sensitive : bool
        Whether the search should be case sensitive, by default False.
    min_version : Optional[VersionType]
        The minimum version of the package to search for, by default None.
    max_version : Optional[VersionType]
        The maximum version of the package to search for, by default None.

    Returns
    -------
    bool
        True if any of the artifacts has the given package, False otherwise.
    """
    return any(
        a.has_package(
            package,
            case_sensitive=case_sensitive,
            min_version=min_version,
            max_version=max_version,
        )
        for a in self.artifacts
    )

with_package(package, case_sensitive=False, min_version=None, max_version=None)

Get all artifacts that have a vulnerability affecting the given package.

Parameters:

Name Type Description Default
package str

The name of the package to search for. Supports regular expressions.

required
case_sensitive bool

Case sensitive matching

False
min_version Optional[VersionType]

The minimum version of the package to search for, by default None.

None
max_version Optional[VersionType]

The maximum version of the package to search for, by default None.

None

Returns:

Type Description
ArtifactReport

An artifact report with all artifacts that have a vulnerability affecting the given package.

Source code in harborapi/ext/report.py
def with_package(
    self,
    package: str,
    case_sensitive: bool = False,
    min_version: Optional[VersionType] = None,
    max_version: Optional[VersionType] = None,
) -> "ArtifactReport":
    """Get all artifacts that have a vulnerability affecting the given package.

    Parameters
    ----------
    package : str
        The name of the package to search for.
        Supports regular expressions.
    case_sensitive : bool
        Case sensitive matching
    min_version : Optional[VersionType]
        The minimum version of the package to search for, by default None.
    max_version : Optional[VersionType]
        The maximum version of the package to search for, by default None.

    Returns
    -------
    ArtifactReport
        An artifact report with all artifacts that have a vulnerability affecting
        the given package.
    """
    return ArtifactReport.from_artifacts(
        [
            a
            for a in self.artifacts
            if a.has_package(
                package,
                case_sensitive,
                min_version=min_version,
                max_version=max_version,
            )
        ],
    )

has_severity(severity)

Check if any of the artifacts has a vulnerability with the given severity.

Parameters:

Name Type Description Default
severity Severity

The severity to search for.

required

Returns:

Type Description
bool

True if any of the artifacts has the given severity, False otherwise.

Source code in harborapi/ext/report.py
def has_severity(self, severity: Severity) -> bool:
    """Check if any of the artifacts has a vulnerability with the given severity.

    Parameters
    ----------
    severity : Severity
        The severity to search for.

    Returns
    -------
    bool
        True if any of the artifacts has the given severity, False otherwise.
    """
    return bool(self.with_severity(severity).artifacts)

with_severity(severity)

Get all artifacts that have a report with the given severity.

Parameters:

Name Type Description Default
severity Severity

The severity to search for.

required

Returns:

Type Description
ArtifactReport

An artifact report with all artifacts that have a vulnerability with the given severity.

Source code in harborapi/ext/report.py
def with_severity(self, severity: Severity) -> "ArtifactReport":
    """Get all artifacts that have a report with the given severity.

    Parameters
    ----------
    severity : Severity
        The severity to search for.

    Returns
    -------
    ArtifactReport
        An artifact report with all artifacts that have a vulnerability with the
        given severity.
    """
    return ArtifactReport.from_artifacts(
        [a for a in self.artifacts if a.report.severity == severity]
    )

has_repository(repository, case_sensitive=False)

Check if any of the artifacts belong to the given repository.

Parameters:

Name Type Description Default
repository str

The repository name to search for. Supports regular expressons.

required
case_sensitive bool

Case sensitive search, by default False

False

Returns:

Type Description
bool

Whether any of the artifacts belong to the given repository.

Source code in harborapi/ext/report.py
def has_repository(self, repository: str, case_sensitive: bool = False) -> bool:
    """Check if any of the artifacts belong to the given repository.

    Parameters
    ----------
    repository : str
        The repository name to search for.
        Supports regular expressons.
    case_sensitive : bool, optional
        Case sensitive search, by default False

    Returns
    -------
    bool
        Whether any of the artifacts belong to the given repository.
    """
    return bool(self.with_repository(repository, case_sensitive).artifacts)

with_repository(repositories, case_sensitive=False)

Return a new report with all artifacts belonging to one or more repositories.

Parameters:

Name Type Description Default
repositories Union[str, List[str]]

A repository or a list of repositories to filter for. Supports regular expressions.

required
case_sensitive bool

Case sensitive repository name matching, by default False

False

Returns:

Type Description
ArtifactReport

A new ArtifactReport where all artifacts belong to one of the given repositories.

Source code in harborapi/ext/report.py
def with_repository(
    self, repositories: Union[str, List[str]], case_sensitive: bool = False
) -> "ArtifactReport":
    """Return a new report with all artifacts belonging to one or more repositories.

    Parameters
    ----------
    repositories : Union[str, List[str]]
        A repository or a list of repositories to filter for.
        Supports regular expressions.
    case_sensitive : bool
        Case sensitive repository name matching, by default False

    Returns
    -------
    ArtifactReport
        A new ArtifactReport where all artifacts belong to one of the given
        repositories.
    """
    # Docker doesn't allow upper-case letters in repository names, but
    # I could not find any documentation on whether Harbor allows it.
    # So we'll just assume that it does. Worst case scenario, the `case_sensitive`
    # parameter will be redundant, but that's fine just to ensure compatibility.

    if isinstance(repositories, str):
        repositories = [repositories]
    elif not isinstance(repositories, list) or not all(
        isinstance(r, str) for r in repositories
    ):
        raise TypeError(
            "repositories must be either a string or a list of strings"
        )  # pragma: no cover

    # Make regex pattern for each repository
    # Our cache function only accepts string arguments, but it's fine to not
    # use it here, since this method is not called nearly as often as the underlying
    # `has_*` methods on the ArtifactInfo objects.
    pattern = re.compile(
        "|".join(repositories), flags=re.IGNORECASE if not case_sensitive else 0
    )
    return ArtifactReport.from_artifacts(
        [
            a
            for a in self.artifacts
            if a.repository.name and pattern.match(a.repository.name)
        ]
    )

has_tag(tag)

Check if any of the artifacts has the given tag.

Parameters:

Name Type Description Default
tag str

The tag to search for.

required

Returns:

Type Description
bool

True if any of the artifacts has the given tag, False otherwise.

Source code in harborapi/ext/report.py
def has_tag(self, tag: str) -> bool:
    """Check if any of the artifacts has the given tag.

    Parameters
    ----------
    tag : str
        The tag to search for.

    Returns
    -------
    bool
        True if any of the artifacts has the given tag, False otherwise.
    """
    return any(a.has_tag(tag) for a in self.artifacts)

with_tag(tag)

Return a new report with all artifacts having the given tag.

Parameters:

Name Type Description Default
tag str

The tag to filter for.

required

Returns:

Type Description
ArtifactReport

A new ArtifactReport where all artifacts have the given tag.

Source code in harborapi/ext/report.py
def with_tag(self, tag: str) -> "ArtifactReport":
    """Return a new report with all artifacts having the given tag.

    Parameters
    ----------
    tag : str
        The tag to filter for.

    Returns
    -------
    ArtifactReport
        A new ArtifactReport where all artifacts have the given tag.
    """
    return ArtifactReport.from_artifacts(
        [a for a in self.artifacts if a.has_tag(tag)]
    )