Repository: srl-labs/containerlab
Pull request #1664 (2023-10-23 13:31:56)

Subdirectories in cloned topologies and gitlab support
This PR aims at evolving the "fetching remote repositorys" functionality.

- It is now possible to reference also files that do not reside in the root of a repository.
- There is now a notion of different git webservices
- Gitlab is kind of not that straight forward, since it can also be a selfhosted instance. Right now I have a strings.Contains(url,"gitlab) in place. This might lead to issues, on the other hand if there is no gitlab in the hostname you can simply add a query parameter <url>?gitlab  or if parameters already exist in the URL <url-with-params>&gitlab to trigger using the gitlab implementation.
- The actual git action, is seperated from the parsing of the git webservice URLs
- Two implementations of the git clone exist. ExecGit and GoGit. Whilst ExecGit is the os.exec() variant that uses the git binary and GoGit uses the go-git golang library.
- The GoGit implementation on consecutive execution inspects the already downloaded repository, checks that the repository URL for the remote "origin" equals the provided URL and, if the same branch is referenced as on the previous execution, pulls the remote branch, to make sure the repo is up to date. In case a different branch is referenced, it would fetch the branch, create a local branch for the remote one and checkout the branch. If the branch already exists locally it will jsut be checked out and the remote is pulled to also forward it to the remote repo state. _[This enhanced processing is not implemented in ExecGo. I would also assume that we abandon ExecGo and just go with GoGit. Kept it for now]_

### Open topics:
- How to identify self hosted git deplyments (gitlab/gitea/etc.) github is quite easy, we use the domainname for it. For the others we need a feasible means to determine the git repo kind.
-  No authentication support in place right now.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) Report
> Merging [#1664](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (7f3487f) into [main](https://app.codecov.io/gh/srl-labs/containerlab/commit/cd3c3cfca651de3f5a0667d9182a0ff0f5ce7ca6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (cd3c3cf) will **decrease** coverage by `0.14%`.
> Report is 2 commits behind head on main.
> The diff coverage is `31.30%`.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1664      +/-   ##
==========================================
- Coverage   51.05%   50.91%   -0.14%     
==========================================
  Files         138      138              
  Lines       13339    13409      +70     
==========================================
+ Hits         6810     6827      +17     
- Misses       5776     5827      +51     
- Partials      753      755       +2     
```

| [Files](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) | Coverage Δ | |
|---|---|---|
| [cmd/root.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-Y21kL3Jvb3QuZ28=) | `51.72% <0.00%> (+0.58%)` | :arrow_up: |
| [utils/file.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZmlsZS5nbw==) | `42.72% <0.00%> (-6.47%)` | :arrow_down: |
| [utils/http.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvaHR0cC5nbw==) | `52.38% <55.38%> (-4.08%)` | :arrow_down: |

... and [1 file with indirect coverage changes](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs)

</details>
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) Report
> Merging [#1664](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (7f3487f) into [main](https://app.codecov.io/gh/srl-labs/containerlab/commit/cd3c3cfca651de3f5a0667d9182a0ff0f5ce7ca6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (cd3c3cf) will **decrease** coverage by `0.14%`.
> Report is 2 commits behind head on main.
> The diff coverage is `31.30%`.

> :exclamation: Current head 7f3487f differs from pull request most recent head c503620. Consider uploading reports for the commit c503620 to get more accurate results

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1664      +/-   ##
==========================================
- Coverage   51.05%   50.91%   -0.14%     
==========================================
  Files         138      138              
  Lines       13339    13409      +70     
==========================================
+ Hits         6810     6827      +17     
- Misses       5776     5827      +51     
- Partials      753      755       +2     
```

| [Files](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) | Coverage Δ | |
|---|---|---|
| [cmd/root.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-Y21kL3Jvb3QuZ28=) | `51.72% <0.00%> (+0.58%)` | :arrow_up: |
| [utils/file.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZmlsZS5nbw==) | `42.72% <0.00%> (-6.47%)` | :arrow_down: |
| [utils/http.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvaHR0cC5nbw==) | `52.38% <55.38%> (-4.08%)` | :arrow_down: |

... and [1 file with indirect coverage changes](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs)

</details>
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) Report
> Merging [#1664](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (c503620) into [main](https://app.codecov.io/gh/srl-labs/containerlab/commit/cd3c3cfca651de3f5a0667d9182a0ff0f5ce7ca6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (cd3c3cf) will **decrease** coverage by `0.28%`.
> Report is 2 commits behind head on main.
> The diff coverage is `22.22%`.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1664      +/-   ##
==========================================
- Coverage   51.05%   50.77%   -0.28%     
==========================================
  Files         138      139       +1     
  Lines       13339    13439     +100     
==========================================
+ Hits         6810     6824      +14     
- Misses       5776     5859      +83     
- Partials      753      756       +3     
```

| [Files](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) | Coverage Δ | |
|---|---|---|
| [cmd/root.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-Y21kL3Jvb3QuZ28=) | `49.45% <0.00%> (-1.69%)` | :arrow_down: |
| [utils/http.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvaHR0cC5nbw==) | `64.70% <58.06%> (+8.25%)` | :arrow_up: |
| [utils/file.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZmlsZS5nbw==) | `42.72% <0.00%> (-6.47%)` | :arrow_down: |
| [utils/git.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZ2l0Lmdv) | `0.00% <0.00%> (ø)` | |

... and [2 files with indirect coverage changes](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs)

</details>
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) Report
> Merging [#1664](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (c503620) into [main](https://app.codecov.io/gh/srl-labs/containerlab/commit/cd3c3cfca651de3f5a0667d9182a0ff0f5ce7ca6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (cd3c3cf) will **decrease** coverage by `0.28%`.
> Report is 4 commits behind head on main.
> The diff coverage is `22.22%`.

> :exclamation: Current head c503620 differs from pull request most recent head b6be522. Consider uploading reports for the commit b6be522 to get more accurate results

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1664      +/-   ##
==========================================
- Coverage   51.05%   50.77%   -0.28%     
==========================================
  Files         138      139       +1     
  Lines       13339    13439     +100     
==========================================
+ Hits         6810     6824      +14     
- Misses       5776     5859      +83     
- Partials      753      756       +3     
```

| [Files](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) | Coverage Δ | |
|---|---|---|
| [cmd/root.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-Y21kL3Jvb3QuZ28=) | `49.45% <0.00%> (-1.69%)` | :arrow_down: |
| [utils/http.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvaHR0cC5nbw==) | `64.70% <58.06%> (+8.25%)` | :arrow_up: |
| [utils/file.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZmlsZS5nbw==) | `42.72% <0.00%> (-6.47%)` | :arrow_down: |
| [utils/git.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZ2l0Lmdv) | `0.00% <0.00%> (ø)` | |

... and [2 files with indirect coverage changes](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs)

</details>
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) Report
> Merging [#1664](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (c503620) into [main](https://app.codecov.io/gh/srl-labs/containerlab/commit/cd3c3cfca651de3f5a0667d9182a0ff0f5ce7ca6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (cd3c3cf) will **decrease** coverage by `0.28%`.
> Report is 4 commits behind head on main.
> The diff coverage is `22.22%`.

> :exclamation: Current head c503620 differs from pull request most recent head db108b8. Consider uploading reports for the commit db108b8 to get more accurate results

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1664      +/-   ##
==========================================
- Coverage   51.05%   50.77%   -0.28%     
==========================================
  Files         138      139       +1     
  Lines       13339    13439     +100     
==========================================
+ Hits         6810     6824      +14     
- Misses       5776     5859      +83     
- Partials      753      756       +3     
```

| [Files](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) | Coverage Δ | |
|---|---|---|
| [cmd/root.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-Y21kL3Jvb3QuZ28=) | `49.45% <0.00%> (-1.69%)` | :arrow_down: |
| [utils/http.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvaHR0cC5nbw==) | `64.70% <58.06%> (+8.25%)` | :arrow_up: |
| [utils/file.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZmlsZS5nbw==) | `42.72% <0.00%> (-6.47%)` | :arrow_down: |
| [utils/git.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZ2l0Lmdv) | `0.00% <0.00%> (ø)` | |

... and [2 files with indirect coverage changes](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs)

</details>
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) Report
> Merging [#1664](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (c503620) into [main](https://app.codecov.io/gh/srl-labs/containerlab/commit/cd3c3cfca651de3f5a0667d9182a0ff0f5ce7ca6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (cd3c3cf) will **decrease** coverage by `0.28%`.
> Report is 7 commits behind head on main.
> The diff coverage is `22.22%`.

> :exclamation: Current head c503620 differs from pull request most recent head e4ac450. Consider uploading reports for the commit e4ac450 to get more accurate results

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1664      +/-   ##
==========================================
- Coverage   51.05%   50.77%   -0.28%     
==========================================
  Files         138      139       +1     
  Lines       13339    13439     +100     
==========================================
+ Hits         6810     6824      +14     
- Misses       5776     5859      +83     
- Partials      753      756       +3     
```

| [Files](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) | Coverage Δ | |
|---|---|---|
| [cmd/root.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-Y21kL3Jvb3QuZ28=) | `49.45% <0.00%> (-1.69%)` | :arrow_down: |
| [utils/http.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvaHR0cC5nbw==) | `64.70% <58.06%> (+8.25%)` | :arrow_up: |
| [utils/file.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZmlsZS5nbw==) | `42.72% <0.00%> (-6.47%)` | :arrow_down: |
| [utils/git.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZ2l0Lmdv) | `0.00% <0.00%> (ø)` | |

... and [2 files with indirect coverage changes](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs)

</details>
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) Report
> Merging [#1664](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (c503620) into [main](https://app.codecov.io/gh/srl-labs/containerlab/commit/cd3c3cfca651de3f5a0667d9182a0ff0f5ce7ca6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (cd3c3cf) will **decrease** coverage by `0.28%`.
> Report is 7 commits behind head on main.
> The diff coverage is `22.22%`.

> :exclamation: Current head c503620 differs from pull request most recent head 50b52b2. Consider uploading reports for the commit 50b52b2 to get more accurate results

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1664      +/-   ##
==========================================
- Coverage   51.05%   50.77%   -0.28%     
==========================================
  Files         138      139       +1     
  Lines       13339    13439     +100     
==========================================
+ Hits         6810     6824      +14     
- Misses       5776     5859      +83     
- Partials      753      756       +3     
```

| [Files](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) | Coverage Δ | |
|---|---|---|
| [cmd/root.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-Y21kL3Jvb3QuZ28=) | `49.45% <0.00%> (-1.69%)` | :arrow_down: |
| [utils/http.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvaHR0cC5nbw==) | `64.70% <58.06%> (+8.25%)` | :arrow_up: |
| [utils/file.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZmlsZS5nbw==) | `42.72% <0.00%> (-6.47%)` | :arrow_down: |
| [utils/git.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZ2l0Lmdv) | `0.00% <0.00%> (ø)` | |

... and [2 files with indirect coverage changes](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs)

</details>
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) Report
> Merging [#1664](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (c503620) into [main](https://app.codecov.io/gh/srl-labs/containerlab/commit/cd3c3cfca651de3f5a0667d9182a0ff0f5ce7ca6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (cd3c3cf) will **decrease** coverage by `0.28%`.
> Report is 7 commits behind head on main.
> The diff coverage is `22.22%`.

> :exclamation: Current head c503620 differs from pull request most recent head e804ab2. Consider uploading reports for the commit e804ab2 to get more accurate results

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1664      +/-   ##
==========================================
- Coverage   51.05%   50.77%   -0.28%     
==========================================
  Files         138      139       +1     
  Lines       13339    13439     +100     
==========================================
+ Hits         6810     6824      +14     
- Misses       5776     5859      +83     
- Partials      753      756       +3     
```

| [Files](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) | Coverage Δ | |
|---|---|---|
| [cmd/root.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-Y21kL3Jvb3QuZ28=) | `49.45% <0.00%> (-1.69%)` | :arrow_down: |
| [utils/http.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvaHR0cC5nbw==) | `64.70% <58.06%> (+8.25%)` | :arrow_up: |
| [utils/file.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZmlsZS5nbw==) | `42.72% <0.00%> (-6.47%)` | :arrow_down: |
| [utils/git.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZ2l0Lmdv) | `0.00% <0.00%> (ø)` | |

... and [2 files with indirect coverage changes](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs)

</details>
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) Report
> Merging [#1664](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (c503620) into [main](https://app.codecov.io/gh/srl-labs/containerlab/commit/cd3c3cfca651de3f5a0667d9182a0ff0f5ce7ca6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (cd3c3cf) will **decrease** coverage by `0.28%`.
> Report is 7 commits behind head on main.
> The diff coverage is `22.22%`.

> :exclamation: Current head c503620 differs from pull request most recent head b9ace0e. Consider uploading reports for the commit b9ace0e to get more accurate results

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1664      +/-   ##
==========================================
- Coverage   51.05%   50.77%   -0.28%     
==========================================
  Files         138      139       +1     
  Lines       13339    13439     +100     
==========================================
+ Hits         6810     6824      +14     
- Misses       5776     5859      +83     
- Partials      753      756       +3     
```

| [Files](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) | Coverage Δ | |
|---|---|---|
| [cmd/root.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-Y21kL3Jvb3QuZ28=) | `49.45% <0.00%> (-1.69%)` | :arrow_down: |
| [utils/http.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvaHR0cC5nbw==) | `64.70% <58.06%> (+8.25%)` | :arrow_up: |
| [utils/file.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZmlsZS5nbw==) | `42.72% <0.00%> (-6.47%)` | :arrow_down: |
| [utils/git.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZ2l0Lmdv) | `0.00% <0.00%> (ø)` | |

... and [2 files with indirect coverage changes](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs)

</details>
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) Report
> Merging [#1664](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (b9ace0e) into [main](https://app.codecov.io/gh/srl-labs/containerlab/commit/cd3c3cfca651de3f5a0667d9182a0ff0f5ce7ca6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (cd3c3cf) will **decrease** coverage by `0.64%`.
> Report is 7 commits behind head on main.
> The diff coverage is `20.93%`.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1664      +/-   ##
==========================================
- Coverage   51.05%   50.41%   -0.64%     
==========================================
  Files         138      139       +1     
  Lines       13339    13628     +289     
==========================================
+ Hits         6810     6871      +61     
- Misses       5776     6000     +224     
- Partials      753      757       +4     
```

| [Files](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) | Coverage Δ | |
|---|---|---|
| [cmd/root.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-Y21kL3Jvb3QuZ28=) | `49.45% <0.00%> (-1.69%)` | :arrow_down: |
| [utils/file.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZmlsZS5nbw==) | `42.72% <0.00%> (-6.47%)` | :arrow_down: |
| [utils/http.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvaHR0cC5nbw==) | `68.75% <66.66%> (+12.29%)` | :arrow_up: |
| [utils/git.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZ2l0Lmdv) | `0.00% <0.00%> (ø)` | |

... and [6 files with indirect coverage changes](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs)

</details>
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) Report
> Merging [#1664](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (b9ace0e) into [main](https://app.codecov.io/gh/srl-labs/containerlab/commit/cd3c3cfca651de3f5a0667d9182a0ff0f5ce7ca6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (cd3c3cf) will **decrease** coverage by `0.64%`.
> Report is 7 commits behind head on main.
> The diff coverage is `20.93%`.

> :exclamation: Current head b9ace0e differs from pull request most recent head c7fb985. Consider uploading reports for the commit c7fb985 to get more accurate results

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1664      +/-   ##
==========================================
- Coverage   51.05%   50.41%   -0.64%     
==========================================
  Files         138      139       +1     
  Lines       13339    13628     +289     
==========================================
+ Hits         6810     6871      +61     
- Misses       5776     6000     +224     
- Partials      753      757       +4     
```

| [Files](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) | Coverage Δ | |
|---|---|---|
| [cmd/root.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-Y21kL3Jvb3QuZ28=) | `49.45% <0.00%> (-1.69%)` | :arrow_down: |
| [utils/file.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZmlsZS5nbw==) | `42.72% <0.00%> (-6.47%)` | :arrow_down: |
| [utils/http.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvaHR0cC5nbw==) | `68.75% <66.66%> (+12.29%)` | :arrow_up: |
| [utils/git.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZ2l0Lmdv) | `0.00% <0.00%> (ø)` | |

... and [6 files with indirect coverage changes](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs)

</details>
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) Report
> Merging [#1664](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (b9ace0e) into [main](https://app.codecov.io/gh/srl-labs/containerlab/commit/cd3c3cfca651de3f5a0667d9182a0ff0f5ce7ca6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (cd3c3cf) will **decrease** coverage by `0.64%`.
> Report is 7 commits behind head on main.
> The diff coverage is `20.93%`.

> :exclamation: Current head b9ace0e differs from pull request most recent head c54b4ab. Consider uploading reports for the commit c54b4ab to get more accurate results

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1664      +/-   ##
==========================================
- Coverage   51.05%   50.41%   -0.64%     
==========================================
  Files         138      139       +1     
  Lines       13339    13628     +289     
==========================================
+ Hits         6810     6871      +61     
- Misses       5776     6000     +224     
- Partials      753      757       +4     
```

| [Files](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) | Coverage Δ | |
|---|---|---|
| [cmd/root.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-Y21kL3Jvb3QuZ28=) | `49.45% <0.00%> (-1.69%)` | :arrow_down: |
| [utils/file.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZmlsZS5nbw==) | `42.72% <0.00%> (-6.47%)` | :arrow_down: |
| [utils/http.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvaHR0cC5nbw==) | `68.75% <66.66%> (+12.29%)` | :arrow_up: |
| [utils/git.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZ2l0Lmdv) | `0.00% <0.00%> (ø)` | |

... and [6 files with indirect coverage changes](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs)

</details>
hellt: @steiler can you make unit tests pass?
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) Report
> Merging [#1664](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (b9ace0e) into [main](https://app.codecov.io/gh/srl-labs/containerlab/commit/cd3c3cfca651de3f5a0667d9182a0ff0f5ce7ca6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (cd3c3cf) will **decrease** coverage by `0.64%`.
> Report is 11 commits behind head on main.
> The diff coverage is `20.93%`.

> :exclamation: Current head b9ace0e differs from pull request most recent head 50e9d59. Consider uploading reports for the commit 50e9d59 to get more accurate results

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1664      +/-   ##
==========================================
- Coverage   51.05%   50.41%   -0.64%     
==========================================
  Files         138      139       +1     
  Lines       13339    13628     +289     
==========================================
+ Hits         6810     6871      +61     
- Misses       5776     6000     +224     
- Partials      753      757       +4     
```

| [Files](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) | Coverage Δ | |
|---|---|---|
| [cmd/root.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-Y21kL3Jvb3QuZ28=) | `49.45% <0.00%> (-1.69%)` | :arrow_down: |
| [utils/file.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZmlsZS5nbw==) | `42.72% <0.00%> (-6.47%)` | :arrow_down: |
| [utils/http.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvaHR0cC5nbw==) | `68.75% <66.66%> (+12.29%)` | :arrow_up: |
| [utils/git.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZ2l0Lmdv) | `0.00% <0.00%> (ø)` | |

... and [6 files with indirect coverage changes](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs)

</details>
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) Report
> Merging [#1664](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (50e9d59) into [main](https://app.codecov.io/gh/srl-labs/containerlab/commit/cd3c3cfca651de3f5a0667d9182a0ff0f5ce7ca6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (cd3c3cf) will **decrease** coverage by `0.41%`.
> Report is 11 commits behind head on main.
> The diff coverage is `22.79%`.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1664      +/-   ##
==========================================
- Coverage   51.05%   50.65%   -0.41%     
==========================================
  Files         138      139       +1     
  Lines       13339    13638     +299     
==========================================
+ Hits         6810     6908      +98     
- Misses       5776     5967     +191     
- Partials      753      763      +10     
```

| [Files](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) | Coverage Δ | |
|---|---|---|
| [cmd/root.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-Y21kL3Jvb3QuZ28=) | `50.00% <0.00%> (-1.14%)` | :arrow_down: |
| [utils/file.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZmlsZS5nbw==) | `42.72% <0.00%> (-6.47%)` | :arrow_down: |
| [utils/http.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvaHR0cC5nbw==) | `69.46% <68.96%> (+13.01%)` | :arrow_up: |
| [utils/git.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZ2l0Lmdv) | `0.00% <0.00%> (ø)` | |

... and [8 files with indirect coverage changes](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs)

</details>
steiler: @hellt unittests are fine now.
take a look at functinality, if thats all fine with you and then I'll also add some smoke tests.
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) Report
> Merging [#1664](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (50e9d59) into [main](https://app.codecov.io/gh/srl-labs/containerlab/commit/cd3c3cfca651de3f5a0667d9182a0ff0f5ce7ca6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (cd3c3cf) will **decrease** coverage by `0.41%`.
> Report is 14 commits behind head on main.
> The diff coverage is `22.79%`.

> :exclamation: Current head 50e9d59 differs from pull request most recent head 58e6d47. Consider uploading reports for the commit 58e6d47 to get more accurate results

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1664      +/-   ##
==========================================
- Coverage   51.05%   50.65%   -0.41%     
==========================================
  Files         138      139       +1     
  Lines       13339    13638     +299     
==========================================
+ Hits         6810     6908      +98     
- Misses       5776     5967     +191     
- Partials      753      763      +10     
```

| [Files](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) | Coverage Δ | |
|---|---|---|
| [cmd/root.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-Y21kL3Jvb3QuZ28=) | `50.00% <0.00%> (-1.14%)` | :arrow_down: |
| [utils/file.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZmlsZS5nbw==) | `42.72% <0.00%> (-6.47%)` | :arrow_down: |
| [utils/http.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvaHR0cC5nbw==) | `69.46% <68.96%> (+13.01%)` | :arrow_up: |
| [utils/git.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZ2l0Lmdv) | `0.00% <0.00%> (ø)` | |

... and [8 files with indirect coverage changes](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs)

</details>
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) Report
> Merging [#1664](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (50e9d59) into [main](https://app.codecov.io/gh/srl-labs/containerlab/commit/29bf4912f127d5ccd28e71a59d6938046ac0755b?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (29bf491) will **decrease** coverage by `0.67%`.
> The diff coverage is `22.79%`.

> :exclamation: Current head 50e9d59 differs from pull request most recent head 31b847d. Consider uploading reports for the commit 31b847d to get more accurate results

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1664      +/-   ##
==========================================
- Coverage   51.32%   50.65%   -0.67%     
==========================================
  Files         138      139       +1     
  Lines       13357    13638     +281     
==========================================
+ Hits         6855     6908      +53     
- Misses       5742     5967     +225     
- Partials      760      763       +3     
```

| [Files](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) | Coverage Δ | |
|---|---|---|
| [cmd/root.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-Y21kL3Jvb3QuZ28=) | `50.00% <0.00%> (-1.14%)` | :arrow_down: |
| [utils/file.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZmlsZS5nbw==) | `42.72% <0.00%> (-6.47%)` | :arrow_down: |
| [utils/http.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvaHR0cC5nbw==) | `69.46% <68.96%> (+13.01%)` | :arrow_up: |
| [utils/git.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZ2l0Lmdv) | `0.00% <0.00%> (ø)` | |

... and [1 file with indirect coverage changes](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs)

</details>
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) Report
> Merging [#1664](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (50e9d59) into [main](https://app.codecov.io/gh/srl-labs/containerlab/commit/29bf4912f127d5ccd28e71a59d6938046ac0755b?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (29bf491) will **decrease** coverage by `0.67%`.
> The diff coverage is `22.79%`.

> :exclamation: Current head 50e9d59 differs from pull request most recent head 593d0bc. Consider uploading reports for the commit 593d0bc to get more accurate results

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1664      +/-   ##
==========================================
- Coverage   51.32%   50.65%   -0.67%     
==========================================
  Files         138      139       +1     
  Lines       13357    13638     +281     
==========================================
+ Hits         6855     6908      +53     
- Misses       5742     5967     +225     
- Partials      760      763       +3     
```

| [Files](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) | Coverage Δ | |
|---|---|---|
| [cmd/root.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-Y21kL3Jvb3QuZ28=) | `50.00% <0.00%> (-1.14%)` | :arrow_down: |
| [utils/file.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZmlsZS5nbw==) | `42.72% <0.00%> (-6.47%)` | :arrow_down: |
| [utils/http.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvaHR0cC5nbw==) | `69.46% <68.96%> (+13.01%)` | :arrow_up: |
| [utils/git.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZ2l0Lmdv) | `0.00% <0.00%> (ø)` | |

... and [1 file with indirect coverage changes](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs)

</details>
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) Report
> Merging [#1664](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (50e9d59) into [main](https://app.codecov.io/gh/srl-labs/containerlab/commit/29bf4912f127d5ccd28e71a59d6938046ac0755b?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) (29bf491) will **decrease** coverage by `0.67%`.
> The diff coverage is `22.79%`.

> :exclamation: Current head 50e9d59 differs from pull request most recent head b2aadd9. Consider uploading reports for the commit b2aadd9 to get more accurate results

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1664      +/-   ##
==========================================
- Coverage   51.32%   50.65%   -0.67%     
==========================================
  Files         138      139       +1     
  Lines       13357    13638     +281     
==========================================
+ Hits         6855     6908      +53     
- Misses       5742     5967     +225     
- Partials      760      763       +3     
```

| [Files](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs) | Coverage Δ | |
|---|---|---|
| [cmd/root.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-Y21kL3Jvb3QuZ28=) | `50.00% <0.00%> (-1.14%)` | :arrow_down: |
| [utils/file.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZmlsZS5nbw==) | `42.72% <0.00%> (-6.47%)` | :arrow_down: |
| [utils/http.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvaHR0cC5nbw==) | `69.46% <68.96%> (+13.01%)` | :arrow_up: |
| [utils/git.go](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs#diff-dXRpbHMvZ2l0Lmdv) | `0.00% <0.00%> (ø)` | |

... and [1 file with indirect coverage changes](https://app.codecov.io/gh/srl-labs/containerlab/pull/1664/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=srl-labs)

</details>

Patch:
diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml
index b53f30b1..484890fd 100644
--- a/.github/workflows/cicd.yml
+++ b/.github/workflows/cicd.yml
@@ -132,7 +132,7 @@ jobs:
         if: always()
         with:
           name: coverage
-          path: ./tests/coverage/*
+          path: /tmp/clab-tests/coverage/*
           retention-days: 7
 
   smoke-tests:
@@ -189,7 +189,7 @@ jobs:
         if: always()
         with:
           name: coverage
-          path: ./tests/coverage/*
+          path: /tmp/clab-tests/coverage/*
           retention-days: 7
 
   ext-container-tests:
@@ -242,7 +242,7 @@ jobs:
         if: always()
         with:
           name: coverage
-          path: ./tests/coverage/*
+          path: /tmp/clab-tests/coverage/*
           retention-days: 7
 
   ceos-basic-tests:
@@ -297,7 +297,7 @@ jobs:
         if: always()
         with:
           name: coverage
-          path: ./tests/coverage/*
+          path: /tmp/clab-tests/coverage/*
           retention-days: 7
 
   srlinux-basic-tests:
@@ -358,7 +358,7 @@ jobs:
         if: always()
         with:
           name: coverage
-          path: ./tests/coverage/*
+          path: /tmp/clab-tests/coverage/*
           retention-days: 7
 
   ixiac-one-basic-tests:
@@ -405,7 +405,7 @@ jobs:
         if: always()
         with:
           name: coverage
-          path: ./tests/coverage/*
+          path: /tmp/clab-tests/coverage/*
           retention-days: 7
 
   vxlan-tests:
@@ -437,7 +437,7 @@ jobs:
       - uses: actions/download-artifact@v3
         with:
           name: coverage
-          path: tests/coverage
+          path: /tmp/clab-tests/coverage
       - name: convert Go's binary coverage to text coverage
         run: make convert-coverage
       - name: Upload coverage to codecov
diff --git a/Makefile b/Makefile
index 5e873ede..a15212f3 100644
--- a/Makefile
+++ b/Makefile
@@ -40,12 +40,12 @@ build-with-podman-debug:
 	CGO_ENABLED=1 go build -o $(BINARY) -gcflags=all="-N -l" -race -cover -trimpath -tags "podman exclude_graphdriver_btrfs btrfs_noversion exclude_graphdriver_devicemapper exclude_graphdriver_overlay containers_image_openpgp" main.go
 
 convert-coverage:
-	go tool covdata textfmt -i=./tests/coverage -o coverage.out
+	go tool covdata textfmt -i=/tmp/clab-tests/coverage -o coverage.out
 
 test:
-	rm -rf $$PWD/tests/coverage
-	mkdir -p $$PWD/tests/coverage
-	CGO_ENABLED=1 go test -cover -race ./... -v -covermode atomic -args -test.gocoverdir="$$PWD/tests/coverage"
+	rm -rf /tmp/clab-tests/coverage
+	mkdir -p /tmp/clab-tests/coverage
+	CGO_ENABLED=1 go test -cover -race ./... -v -covermode atomic -args -test.gocoverdir="/tmp/clab-tests/coverage"
 
 
 ifndef runtime
diff --git a/cmd/root.go b/cmd/root.go
index 49ab0df4..0690ea03 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -6,13 +6,13 @@ package cmd
 
 import (
 	"errors"
-	"fmt"
 	"os"
 	"path/filepath"
 	"time"
 
 	log "github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
+	"github.com/srl-labs/containerlab/git"
 	"github.com/srl-labs/containerlab/utils"
 )
 
@@ -111,38 +111,41 @@ func getTopoFilePath(cmd *cobra.Command) error {
 	}
 
 	var err error
-	if utils.IsHttpUri(topo) {
-		switch {
-		case utils.IsGitHubURL(topo):
-			githubURL := utils.NewGithubURL()
-
-			err := githubURL.Parse(topo)
-			if err != nil {
-				return err
-			}
-
-			err = utils.CloneGithubRepo(githubURL)
-			if err != nil {
-				return err
-			}
-
-			err = os.Chdir(githubURL.RepositoryName)
-			if err != nil {
-				return err
-			}
-
-			// once the repo is cloned the topo file is emptied
-			// to ensure that auto find functionality can kick in
-			topo = ""
-
-			// unless the file name is provided in the github url
-			if githubURL.FileName != "" {
-				topo = githubURL.FileName
-			}
-
-		default:
-			return fmt.Errorf("unsupported git repository: %s", topo)
+	if !utils.FileExists(topo) && (utils.IsHttpUri(topo) || utils.IsGitHubShortURL(topo)) {
+		// for short github urls, prepend https://github.com
+		// note that short notation only works for github links
+		if utils.IsGitHubShortURL(topo) {
+			topo = "https://github.com/" + topo
 		}
+
+		repo, err := git.NewRepo(topo)
+		if err != nil {
+			return err
+		}
+
+		// Instantiate the git implementation to use.
+		gitImpl := git.NewGoGit(repo)
+
+		// clone the repo via the Git Implementation
+		err = gitImpl.Clone()
+		if err != nil {
+			return err
+		}
+
+		// prepare the path with the repo based path
+		path := filepath.Join(repo.GetPath()...)
+		// prepend that path with the repo base directory
+		path = filepath.Join(repo.GetName(), path)
+
+		// change dir to the
+		err = os.Chdir(path)
+		if err != nil {
+			return err
+		}
+
+		// once the repo is cloned the topo file is emptied
+		// to ensure that auto find functionality can kick in
+		topo = repo.GetFilename()
 	}
 
 	// if topo or name flags have been provided, don't try to derive the topo file
diff --git a/docs/cmd/deploy.md b/docs/cmd/deploy.md
index e02351ba..17833027 100644
--- a/docs/cmd/deploy.md
+++ b/docs/cmd/deploy.md
@@ -26,20 +26,22 @@ It is possible to read the topology file from stdin by passing `-` as a value to
 
 ##### Remote topology files
 
-To simplify the deployment of labs that are stored in remote version control systems, containerlab supports the use of remote topology files for Github.
+To simplify the deployment of labs that are stored in remote version control systems, containerlab supports the use of remote topology files for GitHub.com and GitLab.com hosted projects.
 
-By specifying an URL to a repository or a `.clab.yml` file in a repository, containerlab will automatically clone[^1] the repository in your current directory and deploy it. If the URL points to a `.clab.yml` file, containerlab will clone the repository and deploy the lab defined in the file.
+By specifying a URL to a repository or a `.clab.yml` file in a repository, containerlab will automatically clone[^1] the repository in your current directory and deploy it. If the URL points to a `.clab.yml` file, containerlab will clone the repository and deploy the lab defined in the file.
 
 The following URL formats are supported:
 
-| Type                                  | Example                                                            | Which topology file is used                                                        |
-| ------------------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------- |
-| Link to github repository             | https://github.com/hellt/clab-test-repo/                           | An auto-find procedure will find a `clab.yml` in the repository root and deploy it |
-| Link to a file in a github repository | https://github.com/hellt/clab-test-repo/blob/main/lab1.clab.yml    | A file specified in the URL will be deployed                                       |
-| Link to a repo's branch               | https://github.com/hellt/clab-test-repo/tree/branch1               | A branch of a repo is cloned and auto-find procedure kicks in                      |
-| Link to a file in a branch of a repo  | https://github.com/hellt/clab-test-repo/blob/branch1/lab2.clab.yml | A branch is cloned and a file specified in the URL is used for deployment          |
+| Type                                  | Example                                                             | Which topology file is used                                                        |
+| ------------------------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
+| Link to github repository             | https://github.com/hellt/clab-test-repo/                            | An auto-find procedure will find a `clab.yml` in the repository root and deploy it |
+| Link to a file in a github repository | https://github.com/hellt/clab-test-repo/blob/main/lab1.clab.yml     | A file specified in the URL will be deployed                                       |
+| Link to a repo's branch               | https://github.com/hellt/clab-test-repo/tree/branch1                | A branch of a repo is cloned and auto-find procedure kicks in                      |
+| Link to a file in a branch of a repo  | https://github.com/hellt/clab-test-repo/blob/branch1/lab2.clab.yml  | A branch is cloned and a file specified in the URL is used for deployment          |
+| Link to a file in a subdir of a repo  | https://github.com/hellt/clab-test-repo/blob/main/dir/lab3.clab.yml | A file specified in the subdir of the branch will be deployed                      |
+| Shortcut of a github project          | hellt/clab-test-repo                                                | An auto-find procedure will find a `clab.yml` in the repository root and deploy it |
 
-When the lab is deployed using the URL, the repository is cloned in the current working directory. If the repository is already cloned, the clone operation will fail.
+When the lab is deployed using the URL, the repository is cloned in the current working directory. If the repository is already cloned it will be used and not cloned again; containerlab will try to fetch the latest changes from the remote repository.
 
 Subsequent lab operations (such as destroy) must use the filesystem path to the topology file and not the URL.
 
diff --git a/docs/lab-examples/vsrx01.md b/docs/lab-examples/vsrx01.md
new file mode 100644
index 00000000..e696bae4
--- /dev/null
+++ b/docs/lab-examples/vsrx01.md
@@ -0,0 +1,90 @@
+|                               |                                                                                    |
+| ----------------------------- | ---------------------------------------------------------------------------------- |
+| **Description**               | A Juniper vSRX connected to two Alpine Linux Hosts                                 |
+| **Components**                | [Juniper vSRX][vsrx], [Multitool Alpine Linux][client]                             |
+| **Resource requirements**[^1] | :fontawesome-solid-microchip: 2 <br/>:fontawesome-solid-memory: 4 GB               |
+| **Topology file**             | [vsrx01.yml][topofile]                                                             |
+| **Name**                      | vsrx01                                                                             |
+| **Version information**[^2]   | `containerlab:0.47.2`, `junos-vsrx3-x86-64-23.2R1.13.qcow2`, `docker:24.0.6`       |
+
+## Description
+
+This lab consists of one Juniper vSRX router connected to two Alpine Linux nodes.
+
+```
+client1<---->vSRX<---->client2
+```
+
+### Configuration
+
+The vSRX takes about 5 minutes to complete its start up. Check using "docker container ls" until the vSRX shows up as "healthy"
+
+```
+# docker container ls
+CONTAINER ID   IMAGE                                  COMMAND                  CREATED          STATUS                    PORTS                                        NAMES
+85e3251a27c1   vrnetlab/vr-vsrx:23.2R1.13             "/launch.py --userna…"   10 minutes ago   Up 10 minutes (healthy)   22/tcp, 830/tcp, 5000/tcp, 10000-10099/tcp   clab-vsrx1-srx1
+f06a4997ac1b   wbitt/network-multitool:alpine-extra   "/bin/sh /docker-ent…"   10 minutes ago   Up 10 minutes             80/tcp, 443/tcp, 1180/tcp, 11443/tcp         clab-vsrx1-client1
+c77b68244805   wbitt/network-multitool:alpine-extra   "/bin/sh /docker-ent…"   10 minutes ago   Up 10 minutes             80/tcp, 443/tcp, 1180/tcp, 11443/tcp         clab-vsrx1-client2
+```
+
+#### vsrx1
+
+Log into the vSRX using SSH with `ssh admin@clab-vsrx1-srx1` and add the configuration from srx01.cfg. Password is `admin@123`.
+
+```
+admin>configure
+set interfaces ge-0/0/0 unit 0 family inet address 192.168.1.1/30
+set interfaces ge-0/0/1 unit 0 family inet address 192.168.2.1/30
+set security zones security-zone trust interfaces ge-0/0/0 host-inbound-traffic system-services all
+set security zones security-zone trust interfaces ge-0/0/1 host-inbound-traffic system-services all
+set system services web-management https system-generated-certificate
+set security forwarding-options family mpls mode packet-based
+# commit 
+```
+
+#### client1
+
+The two clients should be configured with the correct IP addresses and a route to the other client via the vSRX.
+First attach to the container process `docker exec -it clab-vsrx1-client1 ash`
+
+```
+docker exec -it clab-vsrx1-client1 ash
+
+# ip a show dev eth1
+131: eth1@if132: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9500 qdisc noqueue state UP group default
+   link/ether aa:c1:ab:ac:1b:19 brd ff:ff:ff:ff:ff:ff link-netnsid 1
+   inet 192.168.1.2/30 scope global eth1
+      valid_lft forever preferred_lft forever
+   inet6 fe80::a8c1:abff:feac:1b19/64 scope link
+      valid_lft forever preferred_lft forever
+
+# ip route
+default via 172.20.20.1 dev eth0
+172.20.20.0/24 dev eth0 proto kernel scope link src 172.20.20.4
+192.168.1.0/30 dev eth1 proto kernel scope link src 192.168.1.2
+192.168.2.0/30 via 192.168.1.1 dev eth1
+```
+
+### Verification
+
+Traceroute from client1 to client2 to verify the dataplane via the vSRX.
+
+#### client1
+
+```
+# traceroute 192.168.2.2
+traceroute to 192.168.2.2 (192.168.2.2), 30 hops max, 46 byte packets
+1  192.168.1.1 (192.168.1.1)  0.397 ms  0.347 ms  0.290 ms
+2  192.168.2.2 (192.168.2.2)  0.263 ms  0.374 ms  0.762 ms
+```
+
+#### vSRX Web Gui
+
+To access the vSRX web interface point a browsers at the vSRX management IP address (fxp0) and use https. Login is `admin/admin@123`.
+  
+[vsrx]: https://www.juniper.net/us/en/products/security/srx-series/vsrx-virtual-firewall-datasheet.html
+[client]: https://github.com/wbitt/Network-MultiTool
+[topofile]: https://github.com/srl-labs/containerlab/tree/main/lab-examples/vsrx1/vsrx01.yml
+
+[^1]: Resource requirements are provisional. Consult with the installation guides for additional information.
+[^2]: The lab has been validated using these versions of the required tools/components. Using versions other than stated might lead to a non-operational setup process.
diff --git a/docs/manual/kinds/vr-vsrx.md b/docs/manual/kinds/vr-vsrx.md
index af42cf65..35e442a3 100644
--- a/docs/manual/kinds/vr-vsrx.md
+++ b/docs/manual/kinds/vr-vsrx.md
@@ -63,4 +63,6 @@ Configuration is applied after the node is started. Thus it can contain partial
 
 ## Lab examples
 
-Coming soon.
+The following simple lab consists of two Linux hosts connected via one vSRX:
+
+* [SR Linux and cRPD](../../lab-examples/vsrx01.md)
diff --git a/docs/manual/nodes.md b/docs/manual/nodes.md
index 63fd5ed4..b987e5f3 100644
--- a/docs/manual/nodes.md
+++ b/docs/manual/nodes.md
@@ -166,6 +166,7 @@ The remote file will be downloaded to the containerlab's temp directory at `$TMP
 
     * Upon deletion of a lab, the downloaded startup-config files will not be removed. A manual cleanup should be performed if required.
     * If a lab is redeployed with the lab name and startup-config paths unchanged, the local file will be overwritten.
+    * For https locations the certificates won't be verified to allow fetching artifacts from servers with self-signed certificates.
 
 ### enforce-startup-config
 
diff --git a/git/git.go b/git/git.go
new file mode 100644
index 00000000..436ff5f7
--- /dev/null
+++ b/git/git.go
@@ -0,0 +1,272 @@
+package git
+
+import (
+	"bytes"
+	"fmt"
+	"os"
+	"os/exec"
+
+	gogit "github.com/go-git/go-git/v5"
+	"github.com/go-git/go-git/v5/config"
+	"github.com/go-git/go-git/v5/plumbing"
+	"github.com/go-git/go-git/v5/storage/memory"
+	log "github.com/sirupsen/logrus"
+)
+
+type GoGit struct {
+	gitRepo GitRepo
+	r       *gogit.Repository
+}
+
+// make sure GoGit satisfies the Git interface
+var _ Git = (*GoGit)(nil)
+
+func NewGoGit(gitRepo GitRepo) *GoGit {
+
+	return &GoGit{
+		gitRepo: gitRepo,
+	}
+}
+
+// Clone takes the given GitRepo reference and clones the repo
+// with its internal implementation.
+func (g *GoGit) Clone() error {
+	// if the directory is not present
+	if s, err := os.Stat(g.gitRepo.GetName()); os.IsNotExist(err) {
+		return g.cloneNonExisting()
+	} else if s.IsDir() {
+		return g.cloneExistingRepo()
+	}
+	return fmt.Errorf("error %q exists already but is a file", g.gitRepo.GetName())
+}
+
+func (g *GoGit) getDefaultBranch() (string, error) {
+
+	rem := gogit.NewRemote(memory.NewStorage(), &config.RemoteConfig{
+		Name: "origin",
+		URLs: []string{g.gitRepo.GetCloneURL().String()},
+	})
+
+	// We can then use every Remote functions to retrieve wanted information
+	refs, err := rem.List(&gogit.ListOptions{})
+	if err != nil {
+		return "", err
+	}
+
+	for _, ref := range refs {
+		if ref.Type() == plumbing.SymbolicReference && ref.Name() == plumbing.HEAD {
+			return ref.Target().Short(), nil
+		}
+	}
+
+	return "", fmt.Errorf("unable to determine default branch for %q", g.gitRepo.GetCloneURL().String())
+
+}
+
+func (g *GoGit) openRepo() error {
+	var err error
+
+	// load the git repository
+	g.r, err = gogit.PlainOpen(g.gitRepo.GetName())
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (g *GoGit) cloneExistingRepo() error {
+	log.Debugf("loading git repository %q", g.gitRepo.GetName())
+
+	// open the existing repo
+	err := g.openRepo()
+	if err != nil {
+		return err
+	}
+
+	// loading remote
+	remote, err := g.r.Remote("origin")
+	if err != nil {
+		return err
+	}
+
+	// checking that the configured remote equals the provided remote
+	if remote.Config().URLs[0] != g.gitRepo.GetCloneURL().String() {
+		return fmt.Errorf("repository url of %q differs (%q) from the provided url (%q). stopping",
+			g.gitRepo.GetName(), remote.Config().URLs[0], g.gitRepo.GetCloneURL().String())
+	}
+
+	// get the worktree reference
+	tree, err := g.r.Worktree()
+	if err != nil {
+		return err
+	}
+
+	// resolve the branch
+	// the branch ref from the URL might be empty -> ""
+	// then we need to figure out whats the default branch main / master / sth. else.
+	branch := g.gitRepo.GetBranch()
+	if branch == "" {
+		log.Debugf("default branch not set. determining it")
+		branch, err = g.getDefaultBranch()
+		if err != nil {
+			return err
+		}
+		log.Debugf("default branch is %q", branch)
+	}
+
+	// prepare the checkout options
+	checkoutOpts := &gogit.CheckoutOptions{
+		Branch: plumbing.NewBranchReferenceName(branch),
+	}
+
+	// check if the branch already exists locally.
+	// if not fetch it and check it out.
+
+	if _, err = g.r.Reference(plumbing.NewBranchReferenceName(branch), false); err != nil {
+		err = g.fetchNonExistingBranch(branch)
+		if err != nil {
+			return err
+		}
+
+		ref, err := g.r.Reference(plumbing.NewRemoteReferenceName("origin", branch), true)
+		if err != nil {
+			return err
+		}
+
+		checkoutOpts.Hash = ref.Hash()
+		checkoutOpts.Create = true
+	}
+
+	log.Debugf("checking out branch %q", branch)
+
+	// execute the checkout
+	err = tree.Checkout(checkoutOpts)
+	if err != nil {
+		return err
+	}
+
+	log.Debug("pulling latest repo data")
+	// init the pull options
+	pullOpts := &gogit.PullOptions{
+		Depth:        1,
+		SingleBranch: true,
+		Force:        true,
+	}
+	// execute the pull
+	err = tree.Pull(pullOpts)
+	if err == gogit.NoErrAlreadyUpToDate {
+		log.Debugf("git repository up to date")
+		err = nil
+	}
+
+	return err
+}
+
+func (g *GoGit) fetchNonExistingBranch(branch string) error {
+	// init the remote
+	remote, err := g.r.Remote("origin")
+	if err != nil {
+		return err
+	}
+
+	// build the RefSpec, that wires the remote to the locla branch
+	localRef := plumbing.NewBranchReferenceName(branch)
+	remoteRef := plumbing.NewRemoteReferenceName("origin", branch)
+	refSpec := config.RefSpec(fmt.Sprintf("+%s:%s", localRef, remoteRef))
+
+	// init fetch options
+	fetchOpts := &gogit.FetchOptions{
+		Depth:    1,
+		RefSpecs: []config.RefSpec{refSpec},
+	}
+
+	// execute the fetch
+	err = remote.Fetch(fetchOpts)
+	if err == gogit.NoErrAlreadyUpToDate {
+		log.Debugf("git repository up to date")
+	} else if err != nil {
+		return err
+	}
+
+	// make sure the branch is also showing up in .git/config
+	err = g.r.CreateBranch(&config.Branch{
+		Name:   branch,
+		Remote: "origin",
+		Merge:  localRef,
+	})
+
+	return err
+}
+
+func (g *GoGit) cloneNonExisting() error {
+	var err error
+	// init clone options
+	co := &gogit.CloneOptions{
+		Depth:        1,
+		URL:          g.gitRepo.GetCloneURL().String(),
+		SingleBranch: true,
+	}
+	// set brach reference if set
+	if g.gitRepo.GetBranch() != "" {
+		co.ReferenceName = plumbing.NewBranchReferenceName(g.gitRepo.GetBranch())
+	} else {
+		branchName, err := g.getDefaultBranch()
+		if err != nil {
+			return err
+		}
+		co.ReferenceName = plumbing.NewBranchReferenceName(branchName)
+	}
+
+	// perform clone
+	g.r, err = gogit.PlainClone(g.gitRepo.GetName(), false, co)
+
+	return err
+}
+
+type ExecGit struct {
+	gitRepo GitRepo
+}
+
+// make sure ExecGit satisfies the Git interface
+var _ Git = (*ExecGit)(nil)
+
+func NewExecGit(gitRepo GitRepo) *ExecGit {
+	return &ExecGit{
+		gitRepo: gitRepo,
+	}
+}
+
+// Clone takes the given GitRepo reference and clones the repo
+// with its internal implementation.
+func (g *ExecGit) Clone() error {
+	// build the URL with owner and repo name
+	repoUrl := g.gitRepo.GetCloneURL().String()
+	cloneArgs := []string{"clone", repoUrl, "--depth", "1"}
+	if g.gitRepo.GetBranch() != "" {
+		cloneArgs = append(cloneArgs, []string{"--branch", g.gitRepo.GetBranch()}...)
+	}
+
+	cmd := exec.Command("git", cloneArgs...)
+
+	log.Infof("cloning %q", repoUrl)
+
+	cmd.Stdout = log.New().Writer()
+
+	var stderr bytes.Buffer
+	cmd.Stderr = &stderr
+
+	err := cmd.Run()
+	if err != nil {
+		log.Errorf("failed to clone %q: %v", repoUrl, err)
+		log.Error(stderr.String())
+		return err
+	}
+
+	return nil
+}
+
+type Git interface {
+	// Clone takes the given GitRepo reference and clones the repo
+	// with its internal implementation.
+	Clone() error
+}
diff --git a/git/github.go b/git/github.go
new file mode 100644
index 00000000..c9f49da1
--- /dev/null
+++ b/git/github.go
@@ -0,0 +1,96 @@
+package git
+
+import (
+	"fmt"
+
+	neturl "net/url"
+	"strings"
+)
+
+// NewGitHubRepoFromURL parses the given url and returns a GitHubRepo.
+func NewGitHubRepoFromURL(url *neturl.URL) (*GitHubRepo, error) {
+	r := &GitHubRepo{
+		GitRepoStruct: GitRepoStruct{
+			URL: url,
+		}}
+
+	// trimming the leading and trailing slashes
+	// so that splitPath will have the slashes between the elements only
+	splitPath := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
+
+	// path needs to hold at least 2 elements,
+	// user / org and repo
+	if len(splitPath) < 2 || splitPath[0] == "" || splitPath[1] == "" {
+		return nil, fmt.Errorf("%w %s", errInvalidURL, r.URL.String())
+	}
+
+	// github.dev links can be cloned using github.com
+	if r.URL.Host == "github.dev" {
+		r.URL.Host = "github.com"
+	}
+
+	// set CloneURL to the copy of the original URL
+	// this is overriden later for repo urls with
+	// paths containing blob or tree elements
+	r.CloneURL = &neturl.URL{}
+	*r.CloneURL = *r.URL
+
+	// remove trailing slash from the path
+	// as it bears no meaning for the clone url
+	r.CloneURL.Path = strings.TrimSuffix(r.CloneURL.Path, "/")
+
+	r.ProjectOwner = splitPath[0]
+
+	// in case repo url has a trailing .git suffix, trim it
+	r.RepositoryName = strings.TrimSuffix(splitPath[1], ".git")
+
+	switch {
+	case len(splitPath) == 2:
+		return r, nil
+	case len(splitPath) < 4:
+		return nil, fmt.Errorf("%w invalid github path. should have either 2 or >= 4 path elements", errInvalidURL)
+	}
+
+	r.GitBranch = splitPath[3]
+
+	switch {
+	// path points to a file at a specific git ref
+	case splitPath[2] == "blob":
+		if !(strings.HasSuffix(r.URL.Path, ".yml") || strings.HasSuffix(r.URL.Path, ".yaml")) {
+			return nil, fmt.Errorf("%w: topology file must have yml or yaml extension", errInvalidURL)
+		}
+
+		if len(splitPath)-1 > 4 {
+			r.Path = splitPath[4 : len(splitPath)-1]
+		}
+
+		// overriding CloneURL Path to point to the git repo
+		r.CloneURL.Path = "/" + splitPath[0] + "/" + splitPath[1]
+
+		r.FileName = splitPath[len(splitPath)-1]
+
+	// path points to a git ref (branch or tag)
+	case splitPath[2] == "tree":
+		if len(splitPath) > 4 {
+			r.Path = splitPath[4:]
+		}
+
+		// overriding CloneURL Path to point to the git repo
+		r.CloneURL.Path = "/" + splitPath[0] + "/" + splitPath[1]
+
+		r.FileName = "" // no filename, a dir is referenced
+	}
+
+	return r, nil
+}
+
+// IsGitHubURL checks if the url is a github url.
+func IsGitHubURL(url *neturl.URL) bool {
+	return strings.Contains(url.Host, "github.com") ||
+		strings.Contains(url.Host, "github.dev")
+}
+
+// GitHubRepo struct holds the parsed github url.
+type GitHubRepo struct {
+	GitRepoStruct
+}
diff --git a/git/gitlab.go b/git/gitlab.go
new file mode 100644
index 00000000..b1882b51
--- /dev/null
+++ b/git/gitlab.go
@@ -0,0 +1,96 @@
+package git
+
+import (
+	"fmt"
+	neturl "net/url"
+	"strings"
+)
+
+// IsGitLabURL returns true if the given url is a gitlab url.
+func IsGitLabURL(u *neturl.URL) bool {
+	// besides simply looking for gitlab substring in the url,
+	// we should consider fetching the http response and check
+	// for gitlab string in the body.
+	return strings.Contains(u.String(), "gitlab")
+}
+
+// GitLabRepo represents a gitlab repository.
+type GitLabRepo struct {
+	GitRepoStruct
+}
+
+// NewGitLabRepoFromURL parses the given url and returns a GitLabRepo.
+func NewGitLabRepoFromURL(url *neturl.URL) (*GitLabRepo, error) {
+	r := &GitLabRepo{
+		GitRepoStruct: GitRepoStruct{
+			URL: url,
+		}}
+
+	// trimming the leading and trailing slashes
+	// so that splitPath will have the slashes between the elements only
+	splitPath := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
+
+	// path needs to hold at least 2 elements,
+	// user / org and repo
+	if len(splitPath) < 2 || splitPath[0] == "" || splitPath[1] == "" {
+		return nil, fmt.Errorf("%w %s", errInvalidURL, r.URL.String())
+	}
+
+	// set CloneURL to the copy of the original URL
+	// this is overriden later for repo urls with
+	// paths containing blob or tree elements
+	r.CloneURL = &neturl.URL{}
+	*r.CloneURL = *r.URL
+	// remove raw query from the clone url
+	// raw query is ref_type=heads
+	// in https://gitlab.com/rdodin/clab-test-repo/-/tree/branch1?ref_type=heads
+	r.CloneURL.RawQuery = ""
+
+	// remove trailing slash from the path
+	// as it bears no meaning for the clone url
+	r.CloneURL.Path = strings.TrimSuffix(r.CloneURL.Path, "/")
+
+	r.ProjectOwner = splitPath[0]
+
+	// in case repo url has a trailing .git suffix, trim it
+	r.RepositoryName = strings.TrimSuffix(splitPath[1], ".git")
+
+	switch {
+	case len(splitPath) == 2:
+		return r, nil
+	case len(splitPath) < 5:
+		return nil, fmt.Errorf("%w invalid github path. should have either 2 or >= 5 path elements", errInvalidURL)
+	}
+
+	r.GitBranch = splitPath[4]
+
+	switch {
+	// path points to a file at a specific git ref
+	case splitPath[3] == "blob":
+		if !(strings.HasSuffix(r.URL.Path, ".yml") || strings.HasSuffix(r.URL.Path, ".yaml")) {
+			return nil, fmt.Errorf("%w: topology file must have yml or yaml extension", errInvalidURL)
+		}
+
+		if len(splitPath)-1 > 5 {
+			r.Path = splitPath[5 : len(splitPath)-1]
+		}
+
+		// overriding CloneURL Path to point to the git repo
+		r.CloneURL.Path = "/" + splitPath[0] + "/" + splitPath[1]
+
+		r.FileName = splitPath[len(splitPath)-1]
+
+	// path points to a git ref (branch or tag)
+	case splitPath[3] == "tree":
+		if len(splitPath) > 5 {
+			r.Path = splitPath[5:]
+		}
+
+		// overriding CloneURL Path to point to the git repo
+		r.CloneURL.Path = "/" + splitPath[0] + "/" + splitPath[1]
+
+		r.FileName = "" // no filename, a dir is referenced
+	}
+
+	return r, nil
+}
diff --git a/git/repo.go b/git/repo.go
new file mode 100644
index 00000000..da6bf949
--- /dev/null
+++ b/git/repo.go
@@ -0,0 +1,82 @@
+package git
+
+import (
+	"errors"
+	"net/url"
+)
+
+var errInvalidURL = errors.New("invalid URL")
+
+// GitRepoStruct is a struct that contains all the fields
+// required for a GitRepo instance.
+type GitRepoStruct struct {
+	// original URL parsed from the user input
+	URL *url.URL
+	// CloneURL is the URL that will be used for cloning the repo
+	CloneURL       *url.URL
+	ProjectOwner   string
+	RepositoryName string
+	// GitBranch is the referenced Git branch name.
+	GitBranch string
+	// Path is the path within the repository where the file is located.
+	Path []string
+	// FileName is the name of the file located at repo_root/path/filename.
+	FileName string
+}
+
+// GetFilename returns the filename if a file was specifically referenced.
+// the empty string is returned otherwise.
+func (u *GitRepoStruct) GetFilename() string {
+	return u.FileName
+}
+
+// GetPath returns the path of the repository.
+func (u *GitRepoStruct) GetPath() []string {
+	return u.Path
+}
+
+// GetName returns the repository name.
+func (u *GitRepoStruct) GetName() string {
+	return u.RepositoryName
+}
+
+// GetBranch returns the referenced Git branch name.
+// the empty string is returned otherwise.
+func (u *GitRepoStruct) GetBranch() string {
+	return u.GitBranch
+}
+
+// GetCloneURL returns the CloneURL of the repository
+func (u *GitRepoStruct) GetCloneURL() *url.URL {
+	return u.CloneURL
+}
+
+type GitRepo interface {
+	GetName() string
+	GetFilename() string
+	GetPath() []string
+	GetCloneURL() *url.URL
+	GetBranch() string
+}
+
+// NewRepo parses the given git urlPath and returns an interface
+// that is backed by Github or Gitlab repo implementations.
+func NewRepo(urlPath string) (GitRepo, error) {
+	var r GitRepo
+	var err error
+
+	u, err := url.Parse(urlPath)
+	if err != nil {
+		return nil, err
+	}
+
+	if IsGitHubURL(u) {
+		r, err = NewGitHubRepoFromURL(u)
+	}
+
+	if IsGitLabURL(u) {
+		r, err = NewGitLabRepoFromURL(u)
+	}
+
+	return r, err
+}
diff --git a/go.mod b/go.mod
index fe30dff4..4559a3b3 100644
--- a/go.mod
+++ b/go.mod
@@ -7,7 +7,7 @@ require (
 	github.com/awalterschulze/gographviz v2.0.3+incompatible
 	github.com/containernetworking/plugins v1.3.0
 	github.com/containers/common v0.56.0
-	github.com/containers/podman/v4 v4.7.1
+	github.com/containers/podman/v4 v4.7.2
 	github.com/digitalocean/go-openvswitch v0.0.0-20201214180534-ce0f183468d8
 	github.com/distribution/reference v0.5.0
 	github.com/docker/docker v24.0.7+incompatible
@@ -33,7 +33,7 @@ require (
 	github.com/pmorjan/kmod v1.1.0
 	github.com/scrapli/scrapligo v1.2.0
 	github.com/sirupsen/logrus v1.9.3
-	github.com/spf13/cobra v1.7.0
+	github.com/spf13/cobra v1.8.0
 	github.com/stretchr/testify v1.8.4
 	github.com/tklauser/numcpus v0.6.1
 	github.com/vishvananda/netlink v1.2.1-beta.2
@@ -87,12 +87,14 @@ require (
 	github.com/mistifyio/go-zfs/v3 v3.0.1 // indirect
 	github.com/moby/sys/sequential v0.5.0 // indirect
 	github.com/oklog/ulid v1.3.1 // indirect
+	github.com/pjbgf/sha1cd v0.3.0 // indirect
 	github.com/pkg/sftp v1.13.6 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/secure-systems-lab/go-securesystemslib v0.7.0 // indirect
 	github.com/sigstore/fulcio v1.4.0 // indirect
 	github.com/sigstore/rekor v1.2.2 // indirect
 	github.com/sigstore/sigstore v1.7.3 // indirect
+	github.com/skeema/knownhosts v1.2.0 // indirect
 	github.com/tchap/go-patricia/v2 v2.3.1 // indirect
 	github.com/theupdateframework/go-tuf v0.5.2 // indirect
 	github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
@@ -115,11 +117,11 @@ require (
 	github.com/Masterminds/goutils v1.1.1 // indirect
 	github.com/Microsoft/go-winio v0.6.1 // indirect
 	github.com/Microsoft/hcsshim v0.12.0-rc.0 // indirect
-	github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 // indirect
+	github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
 	github.com/Shopify/ejson v1.3.3 // indirect
 	github.com/VividCortex/ewma v1.2.0 // indirect
 	github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
-	github.com/acomagu/bufpipe v1.0.3 // indirect
+	github.com/acomagu/bufpipe v1.0.4 // indirect
 	github.com/apparentlymart/go-cidr v1.1.0 // indirect
 	github.com/armon/go-metrics v0.4.1 // indirect
 	github.com/aws/aws-sdk-go v1.44.295 // indirect
@@ -173,9 +175,9 @@ require (
 	github.com/fatih/color v1.15.0 // indirect
 	github.com/freddierice/go-losetup v0.0.0-20170407175016-fc9adea44124 // indirect
 	github.com/fsnotify/fsnotify v1.6.0 // indirect
-	github.com/go-git/gcfg v1.5.0 // indirect
-	github.com/go-git/go-billy/v5 v5.3.1 // indirect
-	github.com/go-git/go-git/v5 v5.4.2 // indirect
+	github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
+	github.com/go-git/go-billy/v5 v5.5.0 // indirect
+	github.com/go-git/go-git/v5 v5.9.0
 	github.com/go-logr/logr v1.2.4 // indirect
 	github.com/go-logr/stdr v1.2.2 // indirect
 	github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect
@@ -212,7 +214,6 @@ require (
 	github.com/hashicorp/hcl v1.0.0 // indirect
 	github.com/hashicorp/serf v0.10.1 // indirect
 	github.com/hashicorp/vault/api v1.9.2 // indirect
-	github.com/imdario/mergo v0.3.15 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
 	github.com/jinzhu/copier v0.4.0 // indirect
@@ -263,7 +264,7 @@ require (
 	github.com/vbatts/tar-split v0.11.5 // indirect
 	github.com/vishvananda/netns v0.0.4 // indirect
 	github.com/weaveworks/libgitops v0.0.0-20200611103311-2c871bbbbf0c // indirect
-	github.com/xanzy/ssh-agent v0.3.1 // indirect
+	github.com/xanzy/ssh-agent v0.3.3 // indirect
 	github.com/zealic/xignore v0.3.3 // indirect
 	go.etcd.io/bbolt v1.3.7 // indirect
 	go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect
@@ -273,7 +274,7 @@ require (
 	go4.org/intern v0.0.0-20230205224052-192e9f60865c // indirect
 	go4.org/unsafe/assume-no-moving-gc v0.0.0-20230204201903-c31fa085b70e // indirect
 	gocloud.dev v0.30.0 // indirect
-	golang.org/x/mod v0.13.0
+	golang.org/x/mod v0.14.0
 	golang.org/x/net v0.17.0
 	golang.org/x/oauth2 v0.12.0 // indirect
 	golang.org/x/sync v0.3.0 // indirect
diff --git a/go.sum b/go.sum
index 15cdf7f3..b2227e95 100644
--- a/go.sum
+++ b/go.sum
@@ -727,7 +727,6 @@ github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugX
 github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
 github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
 github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
-github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
 github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
 github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
 github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
@@ -753,9 +752,8 @@ github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb0
 github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM=
-github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
-github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 h1:KLq8BE0KwCL+mmXnjLWEAOYO+2l2AE4YMmqG1ZpZHBs=
-github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
+github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg=
+github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
 github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
 github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
 github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
@@ -775,8 +773,8 @@ github.com/a8m/envsubst v1.4.2 h1:4yWIHXOLEJHQEFd4UjrWDrYeYlV7ncFWJOCBRLOZHQg=
 github.com/a8m/envsubst v1.4.2/go.mod h1:MVUTQNGQ3tsjOOtKCNd+fl8RzhsXcDvvAEzkhGtlsbY=
 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
-github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
-github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
+github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ=
+github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
 github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
 github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY=
@@ -798,8 +796,8 @@ github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY
 github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
 github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
 github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
-github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
+github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0=
 github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI=
@@ -1122,8 +1120,8 @@ github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B
 github.com/containers/ocicrypt v1.1.2/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY=
 github.com/containers/ocicrypt v1.1.8 h1:saSBF0/8DyPUjzcxMVzL2OBUWCkvRvqIm75pu0ADSZk=
 github.com/containers/ocicrypt v1.1.8/go.mod h1:jM362hyBtbwLMWzXQZTlkjKGAQf/BN/LFMtH0FIRt34=
-github.com/containers/podman/v4 v4.7.1 h1:tMhVDvvCIMU64UZVSo2EfLG7ff0/GDHkDg7An2rJ0ZA=
-github.com/containers/podman/v4 v4.7.1/go.mod h1:FJPqIhiwdklJenJskZyoNd1ZNin6kvY6zL9Rypaajxs=
+github.com/containers/podman/v4 v4.7.2 h1:JYElav1y6/w9e4OxPLXZWUC3Tlskui0ysfOYeVSdclQ=
+github.com/containers/podman/v4 v4.7.2/go.mod h1:FJPqIhiwdklJenJskZyoNd1ZNin6kvY6zL9Rypaajxs=
 github.com/containers/psgo v1.8.0 h1:2loGekmGAxM9ir5OsXWEfGwFxorMPYnc6gEDsGFQvhY=
 github.com/containers/psgo v1.8.0/go.mod h1:T8ZxnX3Ur4RvnhxFJ7t8xJ1F48RhiZB4rSrOaR/qGHc=
 github.com/containers/storage v1.43.0/go.mod h1:uZ147thiIFGdVTjMmIw19knttQnUCl3y9zjreHrg11s=
@@ -1156,7 +1154,7 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
-github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -1250,6 +1248,7 @@ github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaB
 github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
 github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
 github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
+github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
 github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
 github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
 github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
@@ -1333,8 +1332,8 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
 github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
 github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
 github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
-github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
 github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
+github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
 github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
 github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
 github.com/go-critic/go-critic v0.3.5-0.20190904082202-d79a9f0c64db/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA=
@@ -1344,19 +1343,18 @@ github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3
 github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=
 github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=
 github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY=
-github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
 github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
+github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
+github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
 github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
-github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
-github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34=
-github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
+github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
+github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
 github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
-github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8=
-github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0=
+github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8=
 github.com/go-git/go-git/v5 v5.0.0/go.mod h1:oYD8y9kWsGINPFJoLdaScGCN6dlKg23blmClfZwtUVA=
 github.com/go-git/go-git/v5 v5.1.0/go.mod h1:ZKfuPUoY1ZqIG4QG9BDBh3G4gLM5zvPuSJAozQrZuyM=
-github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4=
-github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc=
+github.com/go-git/go-git/v5 v5.9.0 h1:cD9SFA7sHVRdJ7AYck1ZaAa/yeuBvGPxwXDL8cxrObY=
+github.com/go-git/go-git/v5 v5.9.0/go.mod h1:RKIqga24sWdMGZF+1Ekv9kylsDz6LzdTSI2s/OsZWE0=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -1947,8 +1945,6 @@ github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH
 github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
-github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM=
-github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
@@ -2066,7 +2062,6 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:C
 github.com/kellerza/template v0.0.6 h1:JNOyQ4tLPcftWYCRVD62m9n4xPR0a0BreZyLuINFQ7g=
 github.com/kellerza/template v0.0.6/go.mod h1:wH8sMB80OBc9SO54GND7ceJBZzar6jpyB6PQ3thO2JE=
 github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
-github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
 github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
@@ -2452,6 +2447,8 @@ github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk
 github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
 github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
 github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
+github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
+github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
 github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
 github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
 github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
@@ -2558,8 +2555,8 @@ github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
-github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
 github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
+github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
 github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
 github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
 github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
@@ -2634,6 +2631,8 @@ github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic
 github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
 github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/skeema/knownhosts v1.2.0 h1:h9r9cf0+u7wSE+M183ZtMGgOJKiL96brpaz5ekfJCpM=
+github.com/skeema/knownhosts v1.2.0/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
 github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
@@ -2663,8 +2662,8 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3
 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
 github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
 github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
-github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
-github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
+github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
+github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
 github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
@@ -2782,9 +2781,8 @@ github.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49
 github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
 github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=
 github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
-github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
-github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo=
-github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w=
+github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
+github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
 github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
 github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
 github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
@@ -3086,8 +3084,8 @@ golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
-golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
+golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -3148,7 +3146,6 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
-golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
 golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
@@ -3368,7 +3365,6 @@ golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
diff --git a/lab-examples/vsrx01/srx1.txt b/lab-examples/vsrx01/srx1.txt
new file mode 100644
index 00000000..94997494
--- /dev/null
+++ b/lab-examples/vsrx01/srx1.txt
@@ -0,0 +1,6 @@
+set interfaces ge-0/0/0 unit 0 family inet address 192.168.1.1/30
+set interfaces ge-0/0/1 unit 0 family inet address 192.168.2.1/30
+set security zones security-zone trust interfaces ge-0/0/0 host-inbound-traffic system-services all
+set security zones security-zone trust interfaces ge-0/0/1 host-inbound-traffic system-services all
+set system services web-management https system-generated-certificate
+set security forwarding-options family mpls mode packet-based
diff --git a/lab-examples/vsrx01/vsrx01.yml b/lab-examples/vsrx01/vsrx01.yml
new file mode 100644
index 00000000..46b29c18
--- /dev/null
+++ b/lab-examples/vsrx01/vsrx01.yml
@@ -0,0 +1,22 @@
+name: vsrx1
+topology:
+  nodes:
+    srx1:
+      kind: vr-vsrx
+      image: vrnetlab/vr-vsrx:23.2R1.13
+      startup-config: srx1.txt 
+    client1:
+      kind: "linux"
+      image: wbitt/network-multitool:alpine-extra
+      exec:
+        - ip addr add 192.168.1.2/30 dev eth1
+        - ip route add 192.168.2.0/30 via 192.168.1.1
+    client2:
+      kind: "linux"
+      image: wbitt/network-multitool:alpine-extra
+      exec:
+        - ip addr add 192.168.2.2/30 dev eth1
+        - ip route add 192.168.1.0/30 via 192.168.2.1
+  links:
+    - endpoints: ["srx1:eth1", "client1:eth1"]
+    - endpoints: ["srx1:eth2", "client2:eth1"]
diff --git a/mkdocs.yml b/mkdocs.yml
index 1b1cccbf..c593f802 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -102,10 +102,11 @@ nav:
       - BGP VPLS between Nokia and Juniper: lab-examples/bgp-vpls-nok-jun.md
       - Keysight IXIA-C and Nokia SR Linux: lab-examples/ixiacone-srl.md
       - Multi-node labs: lab-examples/multinode.md
+      - RARE/freeRtr: lab-examples/rare-freertr.md
+      - Juniper vSRX: lab-examples/vsrx01.md
       - Templated labs:
           - Leaf-spine topology: lab-examples/templated01.md
           - 5-stage Clos topology: lab-examples/templated02.md
-      - RARE/freeRtr: lab-examples/rare-freertr.md
   - Release notes:
       - "0.47": rn/0.47.md
       - "0.46": rn/0.46.md
diff --git a/utils/file.go b/utils/file.go
index eefa4ad0..99f6da69 100644
--- a/utils/file.go
+++ b/utils/file.go
@@ -6,6 +6,7 @@ package utils
 
 import (
 	"bufio"
+	"crypto/tls"
 	"errors"
 	"fmt"
 	"io"
@@ -73,11 +74,18 @@ func CopyFile(src, dst string, mode os.FileMode) (err error) {
 	return CopyFileContents(src, dst, mode)
 }
 
-// IsHttpUri check if the url is a downloadable uri.
+// IsHttpUri checks if the url is a downloadable uri.
+// It returns true for s containing http/https.
 func IsHttpUri(s string) bool {
 	return strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://")
 }
 
+// IsGitHubShortURL returns true for github-friendly short urls
+// such as srl-labs/containerlab.
+func IsGitHubShortURL(s string) bool {
+	return strings.Count(s, "/") == 1 && !strings.HasPrefix(s, "http")
+}
+
 // CopyFileContents copies the contents of the file named src to the file named
 // by dst. The file will be created if it does not already exist. If the
 // destination file exists, all it's contents will be replaced by the contents
@@ -87,7 +95,19 @@ func CopyFileContents(src, dst string, mode os.FileMode) (err error) {
 	var in io.ReadCloser
 
 	if IsHttpUri(src) {
-		resp, err := http.Get(src)
+		// set InsecureSkipVerify to true to allow fetching
+		// files form servers with self-signed certificates
+		tr := &http.Transport{
+			TLSClientConfig: &tls.Config{
+				InsecureSkipVerify: true, // skipcq: GSC-G402
+				MinVersion:         tls.VersionTLS12,
+			},
+		}
+
+		client := &http.Client{Transport: tr}
+
+		// download using client
+		resp, err := client.Get(src)
 		if err != nil || resp.StatusCode != 200 {
 			return fmt.Errorf("%w: %s", errHTTPFetch, src)
 		}
diff --git a/utils/http.go b/utils/http.go
deleted file mode 100644
index cc4c1cd4..00000000
--- a/utils/http.go
+++ /dev/null
@@ -1,110 +0,0 @@
-package utils
-
-import (
-	"bytes"
-	"errors"
-	"fmt"
-	"net/url"
-	"os/exec"
-	"strings"
-
-	log "github.com/sirupsen/logrus"
-)
-
-var errInvalidGithubURL = errors.New("invalid Github URL")
-
-// GithubURL struct holds the parsed github url.
-type GithubURL struct {
-	URLBase        string
-	ProjectOwner   string
-	RepositoryName string
-	GitBranch      string
-	FileName       string
-}
-
-// NewGithubURL returns a pointer to a GithubURI struct
-func NewGithubURL() *GithubURL {
-	return &GithubURL{}
-}
-
-// Parse parses the github.com string url into the GithubURL struct.
-func (u *GithubURL) Parse(ghURL string) error {
-	// strip trailing slash
-	ghURL = strings.TrimSuffix(ghURL, "/")
-
-	parsedURL, err := url.Parse(ghURL)
-	if err != nil {
-		return err
-	}
-
-	splitPath := strings.Split(parsedURL.Path, "/")
-
-	if len(splitPath) < 3 || splitPath[2] == "" {
-		return fmt.Errorf("%w %s", errInvalidGithubURL, ghURL)
-	}
-
-	// github.dev links can be cloned using github.com
-	if parsedURL.Host == "github.dev" {
-		parsedURL.Host = "github.com"
-	}
-
-	u.URLBase = parsedURL.Scheme + "://" + parsedURL.Host
-	u.ProjectOwner = splitPath[1]
-
-	// in case repo url has a trailing .git suffix, trim it
-	splitPath[2] = strings.TrimSuffix(splitPath[2], ".git")
-	u.RepositoryName = splitPath[2]
-
-	switch {
-	// path points to a file at a specific git ref
-	case strings.Contains(ghURL, "/blob/"):
-		if splitPath[len(splitPath)-1] == "" || !(strings.HasSuffix(ghURL, ".yml") || strings.HasSuffix(ghURL, ".yaml")) {
-			return errInvalidGithubURL
-		}
-
-		u.FileName = splitPath[len(splitPath)-1]
-		u.GitBranch = splitPath[len(splitPath)-2]
-
-	// path points to a git ref (branch or tag)
-	case strings.Contains(ghURL, "/tree/"):
-		if splitPath[len(splitPath)-1] == "" {
-			return errInvalidGithubURL
-		}
-
-		u.GitBranch = splitPath[len(splitPath)-1]
-	}
-
-	return nil
-}
-
-// CloneGithubRepo clones the github repo into the current directory.
-func CloneGithubRepo(u *GithubURL) error {
-	cloneArgs := []string{"clone", u.URLBase + "/" + u.ProjectOwner + "/" + u.RepositoryName, "--depth", "1"}
-	if u.GitBranch != "" {
-		cloneArgs = append(cloneArgs, []string{"--branch", u.GitBranch}...)
-	}
-
-	cmd := exec.Command("git", cloneArgs...)
-
-	log.Infof("cloning %s/%s", u.ProjectOwner, u.RepositoryName)
-
-	cmd.Stdout = log.New().Writer()
-
-	var stderr bytes.Buffer
-	cmd.Stderr = &stderr
-
-	err := cmd.Run()
-	if err != nil {
-		log.Errorf("failed to clone %s/%s: %v", u.ProjectOwner, u.RepositoryName, err)
-		log.Error(stderr.String())
-		return err
-	}
-
-	return nil
-}
-
-// IsGitHubURL checks if the url is a github url.
-func IsGitHubURL(url string) bool {
-	return strings.Contains(url, "github.com") ||
-		strings.Contains(url, "github.dev")
-}


Test patch:
diff --git a/.github/workflows/vxlan-tests.yml b/.github/workflows/vxlan-tests.yml
index 37865a66..7563c592 100644
--- a/.github/workflows/vxlan-tests.yml
+++ b/.github/workflows/vxlan-tests.yml
@@ -61,5 +61,5 @@ jobs:
         if: always()
         with:
           name: coverage
-          path: ./tests/coverage/*
+          path: /tmp/clab-tests/coverage/*
           retention-days: 7
diff --git a/git/__repo_parser_test.go b/git/__repo_parser_test.go
new file mode 100644
index 00000000..55d687b5
--- /dev/null
+++ b/git/__repo_parser_test.go
@@ -0,0 +1,586 @@
+package git
+
+import (
+	"errors"
+	"net/url"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestIsGitHubURL(t *testing.T) {
+	tests := []struct {
+		name  string
+		input string
+		want  bool
+	}{
+		{
+			name:  "https://github.com",
+			input: "https://github.com",
+			want:  true,
+		},
+		{
+			name:  "https://github.com/containers/containerlab/blob/master/README.md",
+			input: "https://github.com/containers/containerlab/blob/master/README.md",
+			want:  true,
+		},
+		{
+			name:  "https://google.com/containers",
+			input: "https://google.com/containers",
+			want:  false,
+		},
+		{
+			name:  "https://google.com/containers/containerlab/blob/master/README.md",
+			input: "https://google.com/containers/containerlab/blob/master/README.md",
+			want:  false,
+		},
+		{
+			name:  "https://gitlab.com/containers",
+			input: "https://gitlab.com/containers",
+			want:  false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			u, err := url.Parse(tt.input)
+			if err != nil {
+				t.Errorf("failed parsing url provided in test.")
+			}
+
+			if output := IsGitHubURL(u); output != tt.want {
+				t.Errorf("Test %q failed: want %v, but got %v", tt.name, tt.want, output)
+			}
+		})
+	}
+}
+
+func TestGitHubGitRepoParse(t *testing.T) {
+	tests := []struct {
+		name           string
+		input          string
+		expectedResult *GitHubRepo
+		expectedError  error
+	}{
+		{
+			name:  "bare github url without trailing slash",
+			input: "https://github.com/srl-labs/repo-name",
+			expectedResult: &GitHubRepo{
+				GitRepoStruct: GitRepoStruct{
+					URL: url.URL{
+						Scheme: "https",
+						Host:   "github.com",
+					},
+					Path:           nil,
+					ProjectOwner:   "srl-labs",
+					RepositoryName: "repo-name",
+				},
+			},
+			expectedError: nil,
+		},
+		{
+			name:  "bare github url with trailing slash",
+			input: "https://github.com/srl-labs/repo-name/",
+			expectedResult: &GitHubRepo{
+				GitRepoStruct: GitRepoStruct{
+					URL: url.URL{
+						Scheme: "https",
+						Host:   "github.com",
+					},
+					Path:           nil,
+					ProjectOwner:   "srl-labs",
+					RepositoryName: "repo-name",
+				},
+			},
+			expectedError: nil,
+		},
+		{
+			name:  "bare github.dev url with trailing slash",
+			input: "https://github.dev/srl-labs/repo-name/",
+			expectedResult: &GitHubRepo{
+				GitRepoStruct: GitRepoStruct{
+					URL: url.URL{
+						Scheme: "https",
+						Host:   "github.com",
+					},
+					Path:           nil,
+					ProjectOwner:   "srl-labs",
+					RepositoryName: "repo-name",
+				},
+			},
+			expectedError: nil,
+		},
+		{
+			name:  "bare github url with .git suffix",
+			input: "https://github.com/srl-labs/repo-name.git",
+			expectedResult: &GitHubRepo{
+				GitRepoStruct: GitRepoStruct{
+					URL: url.URL{
+						Scheme: "https",
+						Host:   "github.com",
+					},
+					Path:           nil,
+					ProjectOwner:   "srl-labs",
+					RepositoryName: "repo-name",
+				},
+			},
+			expectedError: nil,
+		},
+		{
+			name:           "invalid url with just org name",
+			input:          "https://github.com/srl-labs/",
+			expectedResult: &GitHubRepo{},
+			expectedError:  errInvalidURL,
+		},
+		{
+			name:           "invalid url with no owner and no org",
+			input:          "https://github.com/",
+			expectedResult: &GitHubRepo{},
+			expectedError:  errInvalidURL,
+		},
+		{
+			name:  "github url with a clab file on the main branch",
+			input: "https://github.com/srl-labs/repo-name/blob/main/file.clab.yml",
+			expectedResult: &GitHubRepo{
+				GitRepoStruct: GitRepoStruct{
+					URL: url.URL{
+						Scheme: "https",
+						Host:   "github.com",
+					},
+					Path:           []string{},
+					ProjectOwner:   "srl-labs",
+					RepositoryName: "repo-name",
+					GitBranch:      "main",
+					FileName:       "file.clab.yml",
+				},
+			},
+			expectedError: nil,
+		},
+		{
+			name:  "github url with a yaml file on the main branch",
+			input: "https://github.com/srl-labs/repo-name/blob/main/file.yaml",
+			expectedResult: &GitHubRepo{
+				GitRepoStruct: GitRepoStruct{
+					URL: url.URL{
+						Scheme: "https",
+						Host:   "github.com",
+					},
+					ProjectOwner:   "srl-labs",
+					RepositoryName: "repo-name",
+					GitBranch:      "main",
+					Path:           []string{},
+					FileName:       "file.yaml",
+				},
+			},
+			expectedError: nil,
+		},
+		{
+			name:           "url with invalid file on the main branch",
+			input:          "https://github.com/srl-labs/repo-name/blob/main/file.foo",
+			expectedResult: &GitHubRepo{},
+			expectedError:  errInvalidURL,
+		},
+		{
+			name:  "github url with a specified git ref and no file",
+			input: "https://github.com/srl-labs/repo-name/tree/some-branch",
+			expectedResult: &GitHubRepo{
+				GitRepoStruct: GitRepoStruct{
+					URL: url.URL{
+						Scheme: "https",
+						Host:   "github.com",
+					},
+					ProjectOwner:   "srl-labs",
+					RepositoryName: "repo-name",
+					GitBranch:      "some-branch",
+					Path:           []string{},
+				},
+			},
+			expectedError: nil,
+		},
+		{
+			name:  "github url with a specified git ref and no file and trailing slash",
+			input: "https://github.com/srl-labs/repo-name/tree/some-branch/",
+			expectedResult: &GitHubRepo{
+				GitRepoStruct: GitRepoStruct{
+					URL: url.URL{
+						Scheme: "https",
+						Host:   "github.com",
+					},
+					ProjectOwner:   "srl-labs",
+					RepositoryName: "repo-name",
+					GitBranch:      "some-branch",
+					Path:           []string{},
+				},
+			},
+			expectedError: nil,
+		},
+		{
+			name:  "github url with ref to file in subdir",
+			input: "https://github.com/srl-labs/containerlab/blob/main/lab-examples/srl01/srl01.clab.yml",
+			expectedResult: &GitHubRepo{
+				GitRepoStruct: GitRepoStruct{
+					URL: url.URL{
+						Scheme: "https",
+						Host:   "github.com",
+					},
+					ProjectOwner:   "srl-labs",
+					RepositoryName: "containerlab",
+					GitBranch:      "main",
+					Path:           []string{"lab-examples", "srl01"},
+					FileName:       "srl01.clab.yml",
+				},
+			},
+			expectedError: nil,
+		},
+		{
+			name:  "github url with ref to subdir",
+			input: "https://github.com/srl-labs/containerlab/tree/main/lab-examples/srl01/",
+			expectedResult: &GitHubRepo{
+				GitRepoStruct: GitRepoStruct{
+					URL: url.URL{
+						Scheme: "https",
+						Host:   "github.com",
+					},
+					ProjectOwner:   "srl-labs",
+					RepositoryName: "containerlab",
+					GitBranch:      "main",
+					Path:           []string{"lab-examples", "srl01"},
+				},
+			},
+			expectedError: nil,
+		},
+		{
+			name:  "github url with tree ref to repo root",
+			input: "https://github.com/srl-labs/containerlab/tree/main",
+			expectedResult: &GitHubRepo{
+				GitRepoStruct: GitRepoStruct{
+					URL: url.URL{
+						Scheme: "https",
+						Host:   "github.com",
+					},
+					ProjectOwner:   "srl-labs",
+					RepositoryName: "containerlab",
+					GitBranch:      "main",
+					Path:           []string{},
+				},
+			},
+			expectedError: nil,
+		},
+		{
+			name:  "github url with tree ref to file in repo root",
+			input: "https://github.com/srl-labs/containerlab/blob/main/mytopo.yml",
+			expectedResult: &GitHubRepo{
+				GitRepoStruct: GitRepoStruct{
+					URL: url.URL{
+						Scheme: "https",
+						Host:   "github.com",
+					},
+					ProjectOwner:   "srl-labs",
+					RepositoryName: "containerlab",
+					GitBranch:      "main",
+					Path:           []string{},
+					FileName:       "mytopo.yml",
+				},
+			},
+			expectedError: nil,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			u, err := url.Parse(tt.input)
+			if err != nil {
+				t.Errorf("failed parsing url provided in test.")
+			}
+			repo, err := ParseGitHubRepoUrl(u)
+
+			if err != nil && tt.expectedError == nil {
+				t.Fatalf("unexpected error: %v", err)
+			}
+
+			if err == nil && tt.expectedError != nil {
+				t.Errorf("expected error: %v, but got nil", tt.expectedError)
+			}
+
+			if err != nil && tt.expectedError != nil {
+				if !errors.Is(err, tt.expectedError) {
+					t.Fatalf("expected error: %v, but got %v", err, tt.expectedError)
+				}
+				// exit the test case as we don't want to compare url structs
+				// since when error is available and matches the expected error
+				// we don't care about the state the struct is in
+				return
+			}
+
+			if diff := cmp.Diff(repo, tt.expectedResult); diff != "" {
+				t.Errorf("got result: = %v, expected %v, diff:\n%s", repo, tt.expectedResult, diff)
+			}
+		})
+	}
+}
+
+func TestParseGitLabRepoUrl(t *testing.T) {
+	tests := []struct {
+		name           string
+		input          string
+		expectedResult *GitLabRepo
+		expectedError  error
+	}{
+		{
+			name:  "bare gitlab url without trailing slash",
+			input: "https://fake.gitlab.com/user/repo-name",
+			expectedResult: &GitLabRepo{
+				GitRepoStruct: GitRepoStruct{
+					URL: url.URL{
+						Scheme: "https",
+						Host:   "fake.gitlab.com",
+					},
+					Path:           nil,
+					ProjectOwner:   "user",
+					RepositoryName: "repo-name",
+				},
+			},
+			expectedError: nil,
+		},
+		{
+			name:  "bare gitlab url with trailing slash",
+			input: "https://fake.gitlab.com/user/repo-name/",
+			expectedResult: &GitLabRepo{
+				GitRepoStruct: GitRepoStruct{
+					URL: url.URL{
+						Scheme: "https",
+						Host:   "fake.gitlab.com",
+					},
+					Path:           nil,
+					ProjectOwner:   "user",
+					RepositoryName: "repo-name",
+				},
+			},
+			expectedError: nil,
+		},
+		{
+			name:  "bare github url with .git suffix",
+			input: "https://fake.gitlab.com/user/repo-name.git",
+			expectedResult: &GitLabRepo{
+				GitRepoStruct: GitRepoStruct{
+					URL: url.URL{
+						Scheme: "https",
+						Host:   "fake.gitlab.com",
+					},
+					Path:           nil,
+					ProjectOwner:   "user",
+					RepositoryName: "repo-name",
+				},
+			},
+			expectedError: nil,
+		},
+		{
+			name:           "invalid url with just org name",
+			input:          "https://fake.gitlab.com/user/",
+			expectedResult: &GitLabRepo{},
+			expectedError:  errInvalidURL,
+		},
+		{
+			name:           "invalid url with no owner and no org",
+			input:          "https:/fake.gitlab.com/",
+			expectedResult: &GitLabRepo{},
+			expectedError:  errInvalidURL,
+		},
+		{
+			name:  "gitlab url with a clab file on the main branch",
+			input: "https://fake.gitlab.com/user/repo-name/-/blob/main/file.clab.yml",
+			expectedResult: &GitLabRepo{
+				GitRepoStruct: GitRepoStruct{
+					URL: url.URL{
+						Scheme: "https",
+						Host:   "fake.gitlab.com",
+					},
+					Path:           []string{},
+					ProjectOwner:   "user",
+					RepositoryName: "repo-name",
+					GitBranch:      "main",
+					FileName:       "file.clab.yml",
+				},
+			},
+			expectedError: nil,
+		},
+		{
+			name:  "gitlab url with a yaml file on the main branch",
+			input: "https://fake.gitlab.com/user/repo-name/-/blob/main/file.yaml",
+			expectedResult: &GitLabRepo{
+				GitRepoStruct: GitRepoStruct{
+					URL: url.URL{
+						Scheme: "https",
+						Host:   "fake.gitlab.com",
+					},
+					ProjectOwner:   "user",
+					RepositoryName: "repo-name",
+					GitBranch:      "main",
+					Path:           []string{},
+					FileName:       "file.yaml",
+				},
+			},
+			expectedError: nil,
+		},
+		{
+			name:           "url with invalid file on the main branch",
+			input:          "https://fake.gitlab.com/user/repo-name/-/blob/main/file.foo",
+			expectedResult: &GitLabRepo{},
+			expectedError:  errInvalidURL,
+		},
+		{
+			name:  "gitlab url with a specified git ref and no file",
+			input: "https://fake.gitlab.com/user/repo-name/-/tree/some-branch",
+			expectedResult: &GitLabRepo{
+				GitRepoStruct: GitRepoStruct{
+					URL: url.URL{
+						Scheme: "https",
+						Host:   "fake.gitlab.com",
+					},
+					ProjectOwner:   "user",
+					RepositoryName: "repo-name",
+					GitBranch:      "some-branch",
+					Path:           []string{},
+				},
+			},
+			expectedError: nil,
+		},
+		{
+			name:  "gitlab url with a specified git ref and no file and trailing slash",
+			input: "https://fake.gitlab.com/user/repo-name/-/tree/some-branch/",
+			expectedResult: &GitLabRepo{
+				GitRepoStruct: GitRepoStruct{
+					URL: url.URL{
+						Scheme: "https",
+						Host:   "fake.gitlab.com",
+					},
+					ProjectOwner:   "user",
+					RepositoryName: "repo-name",
+					GitBranch:      "some-branch",
+					Path:           []string{},
+				},
+			},
+			expectedError: nil,
+		},
+		{
+			name:  "gitlab url with ref to file in subdir",
+			input: "https://fake.gitlab.com/user/containerlab/-/blob/main/lab-examples/srl01/srl01.clab.yml",
+			expectedResult: &GitLabRepo{
+				GitRepoStruct: GitRepoStruct{
+					URL: url.URL{
+						Scheme: "https",
+						Host:   "fake.gitlab.com",
+					},
+					ProjectOwner:   "user",
+					RepositoryName: "containerlab",
+					GitBranch:      "main",
+					Path:           []string{"lab-examples", "srl01"},
+					FileName:       "srl01.clab.yml",
+				},
+			},
+			expectedError: nil,
+		},
+		{
+			name:  "gitlab url with ref to subdir",
+			input: "https://fake.gitlab.com/user/containerlab/-/tree/main/lab-examples/srl01/",
+			expectedResult: &GitLabRepo{
+				GitRepoStruct: GitRepoStruct{
+					URL: url.URL{
+						Scheme: "https",
+						Host:   "fake.gitlab.com",
+					},
+					ProjectOwner:   "user",
+					RepositoryName: "containerlab",
+					GitBranch:      "main",
+					Path:           []string{"lab-examples", "srl01"},
+				},
+			},
+			expectedError: nil,
+		},
+		{
+			name:  "gitlab url with tree ref to repo root",
+			input: "https://fake.gitlab.com/user/containerlab/-/tree/main",
+			expectedResult: &GitLabRepo{
+				GitRepoStruct: GitRepoStruct{
+					URL: url.URL{
+						Scheme: "https",
+						Host:   "fake.gitlab.com",
+					},
+					ProjectOwner:   "user",
+					RepositoryName: "containerlab",
+					GitBranch:      "main",
+					Path:           []string{},
+				},
+			},
+			expectedError: nil,
+		},
+		{
+			name:  "gitlab url with tree ref to file in repo root",
+			input: "https://fake.gitlab.com/user/containerlab/-/blob/main/mytopo.yml",
+			expectedResult: &GitLabRepo{
+				GitRepoStruct: GitRepoStruct{
+					URL: url.URL{
+						Scheme: "https",
+						Host:   "fake.gitlab.com",
+					},
+					ProjectOwner:   "user",
+					RepositoryName: "containerlab",
+					GitBranch:      "main",
+					Path:           []string{},
+					FileName:       "mytopo.yml",
+				},
+			},
+			expectedError: nil,
+		},
+		{
+			name:  "gitlab url with tree ref to file in repo root and query parameters",
+			input: "https://fake.gitlab.com/user/containerlab/-/blob/main/mytopo.yml?foo=bar",
+			expectedResult: &GitLabRepo{
+				GitRepoStruct: GitRepoStruct{
+					URL: url.URL{
+						Scheme: "https",
+						Host:   "fake.gitlab.com",
+					},
+					ProjectOwner:   "user",
+					RepositoryName: "containerlab",
+					GitBranch:      "main",
+					Path:           []string{},
+					FileName:       "mytopo.yml",
+				},
+			},
+			expectedError: nil,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+
+			u, err := url.Parse(tt.input)
+			if err != nil {
+				t.Errorf("failed parsing url provided in test.")
+			}
+			repo, err := ParseGitLabRepoUrl(u)
+
+			if err != nil && tt.expectedError == nil {
+				t.Fatalf("unexpected error: %v", err)
+			}
+
+			if err == nil && tt.expectedError != nil {
+				t.Errorf("expected error: %v, but got nil", tt.expectedError)
+			}
+
+			if err != nil && tt.expectedError != nil {
+				if !errors.Is(err, tt.expectedError) {
+					t.Fatalf("expected error: %v, but got %v", tt.expectedError, err)
+				}
+				// exit the test case as we don't want to compare url structs
+				// since when error is available and matches the expected error
+				// we don't care about the state the struct is in
+				return
+			}
+
+			if diff := cmp.Diff(repo, tt.expectedResult); diff != "" {
+				t.Errorf("got result: = %v, expected %v, diff:\n%s", repo, tt.expectedResult, diff)
+			}
+		})
+	}
+}
diff --git a/git/github_test.go b/git/github_test.go
new file mode 100644
index 00000000..106bcf95
--- /dev/null
+++ b/git/github_test.go
@@ -0,0 +1,232 @@
+package git
+
+import (
+	"errors"
+	"net/url"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+// urlFromStr is a helper function to create a url.URL from a string.
+func urlFromStr(s string) *url.URL {
+	u, err := url.Parse(s)
+	if err != nil {
+		panic(err)
+	}
+	return u
+}
+
+func TestNewGitHubRepoFromURL(t *testing.T) {
+	tests := []struct {
+		name    string
+		url     string
+		repo    *GitHubRepo
+		wantErr bool
+	}{
+		{
+			name: "valid github url without trailing slash and with https schema",
+			url:  "https://github.com/hellt/clab-test-repo",
+			repo: &GitHubRepo{
+				GitRepoStruct{
+					ProjectOwner:   "hellt",
+					RepositoryName: "clab-test-repo",
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "valid github url with trailing slash and with https schema",
+			url:  "https://github.com/hellt/clab-test-repo/",
+			repo: &GitHubRepo{
+				GitRepoStruct{
+					URL:            urlFromStr("https://github.com/hellt/clab-test-repo/"),
+					CloneURL:       urlFromStr("https://github.com/hellt/clab-test-repo"),
+					ProjectOwner:   "hellt",
+					RepositoryName: "clab-test-repo",
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "valid github.dev url without trailing slash and with https schema",
+			url:  "https://github.dev/hellt/clab-test-repo",
+			repo: &GitHubRepo{
+				GitRepoStruct{
+					// github.dev links can be cloned using github.com
+					URL:            urlFromStr("https://github.com/hellt/clab-test-repo"),
+					CloneURL:       urlFromStr("https://github.com/hellt/clab-test-repo"),
+					ProjectOwner:   "hellt",
+					RepositoryName: "clab-test-repo",
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "valid github.dev url with trailing slash and with https schema",
+			url:  "https://github.dev/hellt/clab-test-repo/",
+			repo: &GitHubRepo{
+				GitRepoStruct{
+					// github.dev links can be cloned using github.com
+					URL:            urlFromStr("https://github.com/hellt/clab-test-repo/"),
+					CloneURL:       urlFromStr("https://github.com/hellt/clab-test-repo"),
+					ProjectOwner:   "hellt",
+					RepositoryName: "clab-test-repo",
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name:    "invalid github url with just one path element and https schema and trailing slash",
+			url:     "https://github.com/hellt/",
+			wantErr: true,
+		},
+		{
+			name: "valid github url with https schema and with .git suffix",
+			url:  "https://github.com/hellt/clab-test-repo.git",
+			repo: &GitHubRepo{
+				GitRepoStruct{
+					ProjectOwner:   "hellt",
+					RepositoryName: "clab-test-repo",
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "github url with a clab file on the main branch",
+			url:  "https://github.com/hellt/clab-test-repo/blob/main/lab1.clab.yml",
+			repo: &GitHubRepo{
+				GitRepoStruct{
+					ProjectOwner:   "hellt",
+					RepositoryName: "clab-test-repo",
+					CloneURL:       urlFromStr("https://github.com/hellt/clab-test-repo"),
+					GitBranch:      "main",
+					FileName:       "lab1.clab.yml",
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name:    "invalid file in a github url with on the main branch",
+			url:     "https://github.com/hellt/clab-test-repo/blob/main/lab1.foo",
+			wantErr: true,
+		},
+		{
+			name: "github url with a clab file on the main branch in a nested dir",
+			url:  "https://github.com/hellt/clab-test-repo/blob/main/dir/lab3.clab.yml",
+			repo: &GitHubRepo{
+				GitRepoStruct{
+					ProjectOwner:   "hellt",
+					RepositoryName: "clab-test-repo",
+					CloneURL:       urlFromStr("https://github.com/hellt/clab-test-repo"),
+					GitBranch:      "main",
+					Path:           []string{"dir"},
+					FileName:       "lab3.clab.yml",
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "github url pointing to a branch",
+			url:  "https://github.com/hellt/clab-test-repo/tree/branch1",
+			repo: &GitHubRepo{
+				GitRepoStruct{
+					ProjectOwner:   "hellt",
+					RepositoryName: "clab-test-repo",
+					CloneURL:       urlFromStr("https://github.com/hellt/clab-test-repo"),
+					GitBranch:      "branch1",
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "github url pointing to a file in a branch",
+			url:  "https://github.com/hellt/clab-test-repo/blob/branch1/lab2.clab.yml",
+			repo: &GitHubRepo{
+				GitRepoStruct{
+					ProjectOwner:   "hellt",
+					RepositoryName: "clab-test-repo",
+					CloneURL:       urlFromStr("https://github.com/hellt/clab-test-repo"),
+					GitBranch:      "branch1",
+					FileName:       "lab2.clab.yml",
+				},
+			},
+			wantErr: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			repo, err := NewGitHubRepoFromURL(urlFromStr(tt.url))
+
+			if (err != nil) != tt.wantErr {
+				t.Errorf("ParseGitURL() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+
+			if (err != nil) && errors.Is(err, errInvalidURL) {
+				return
+			}
+
+			// when we do not manipulate url path in the ParseGitURL method
+			// the wantRepo.URL field will be nil, so we need to set it up
+			// to match the original URL
+			if tt.repo.URL == nil {
+				tt.repo.URL = urlFromStr(tt.url)
+			}
+
+			// when we do not manipulate CloneURL in the ParseGitURL method
+			// the wantRepo.CloneURL field will be nil, so we need to set it up
+			// to match the original URL
+			if tt.repo.CloneURL == nil {
+				tt.repo.CloneURL = urlFromStr(tt.url)
+			}
+
+			if diff := cmp.Diff(repo, tt.repo); diff != "" {
+				t.Errorf("TestNewGitLabRepoFromURL() mismatch:\n%s", diff)
+			}
+		})
+	}
+}
+
+func TestIsGitHubURL(t *testing.T) {
+	tests := []struct {
+		name  string
+		input string
+		want  bool
+	}{
+		{
+			name:  "github.com",
+			input: "http://github.com",
+			want:  true,
+		},
+		{
+			name:  "github.com/containers/containerlab/blob/master/README.md",
+			input: "https://github.com/containers/containerlab/blob/master/README.md",
+			want:  true,
+		},
+		{
+			name:  "google.com/containers",
+			input: "http://google.com/containers",
+			want:  false,
+		},
+		{
+			name:  "google.com/containers/containerlab/blob/master/README.md",
+			input: "https://google.com/containers/containerlab/blob/master/README.md",
+			want:  false,
+		},
+		{
+			name:  "gitlab.com/containers",
+			input: "http://gitlab.com/containers",
+			want:  false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if output := IsGitHubURL(urlFromStr(tt.input)); output != tt.want {
+				t.Errorf("Test %q failed: want %v, but got %v", tt.name, tt.want, output)
+			}
+		})
+	}
+}
diff --git a/git/gitlab_test.go b/git/gitlab_test.go
new file mode 100644
index 00000000..dcb5cb69
--- /dev/null
+++ b/git/gitlab_test.go
@@ -0,0 +1,194 @@
+package git
+
+import (
+	"errors"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestNewGitLabRepoFromURL(t *testing.T) {
+	tests := []struct {
+		name    string
+		url     string
+		repo    *GitLabRepo
+		wantErr bool
+	}{
+		{
+			name: "valid gitlab url without trailing slash and with https schema",
+			url:  "https://gitlab.com/rdodin/clab-test-repo",
+			repo: &GitLabRepo{
+				GitRepoStruct{
+					ProjectOwner:   "rdodin",
+					RepositoryName: "clab-test-repo",
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "valid gitlab url with trailing slash and with https schema",
+			url:  "https://gitlab.com/rdodin/clab-test-repo/",
+			repo: &GitLabRepo{
+				GitRepoStruct{
+					URL:            urlFromStr("https://gitlab.com/rdodin/clab-test-repo/"),
+					CloneURL:       urlFromStr("https://gitlab.com/rdodin/clab-test-repo"),
+					ProjectOwner:   "rdodin",
+					RepositoryName: "clab-test-repo",
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name:    "invalid gitlab url with just one path element and https schema and trailing slash",
+			url:     "https://gitlab.com/rdodin/",
+			wantErr: true,
+		},
+		{
+			name: "valid gitlab url with https schema and with .git suffix",
+			url:  "https://gitlab.com/rdodin/clab-test-repo.git",
+			repo: &GitLabRepo{
+				GitRepoStruct{
+					ProjectOwner:   "rdodin",
+					RepositoryName: "clab-test-repo",
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "gitlab url with a clab file on the main branch without path args",
+			url:  "https://gitlab.com/rdodin/clab-test-repo/-/blob/main/lab1.clab.yml",
+			repo: &GitLabRepo{
+				GitRepoStruct{
+					ProjectOwner:   "rdodin",
+					RepositoryName: "clab-test-repo",
+					CloneURL:       urlFromStr("https://gitlab.com/rdodin/clab-test-repo"),
+					GitBranch:      "main",
+					FileName:       "lab1.clab.yml",
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name:    "invalid file in a gitlab url with on the main branch",
+			url:     "https://gitlab.com/rdodin/clab-test-repo/-/blob/main/lab1.foo",
+			wantErr: true,
+		},
+		{
+			name: "gitlab url with a clab file on the main branch in a nested dir without path args",
+			url:  "https://gitlab.com/rdodin/clab-test-repo/-/blob/main/dir/lab3.clab.yml",
+			repo: &GitLabRepo{
+				GitRepoStruct{
+					ProjectOwner:   "rdodin",
+					RepositoryName: "clab-test-repo",
+					CloneURL:       urlFromStr("https://gitlab.com/rdodin/clab-test-repo"),
+					GitBranch:      "main",
+					Path:           []string{"dir"},
+					FileName:       "lab3.clab.yml",
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "gitlab url pointing to a branch with a path arg",
+			url:  "https://gitlab.com/rdodin/clab-test-repo/-/tree/branch1?ref_type=heads",
+			repo: &GitLabRepo{
+				GitRepoStruct{
+					ProjectOwner:   "rdodin",
+					RepositoryName: "clab-test-repo",
+					CloneURL:       urlFromStr("https://gitlab.com/rdodin/clab-test-repo"),
+					GitBranch:      "branch1",
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "gitlab url pointing to a file in a branch",
+			url:  "https://gitlab.com/rdodin/clab-test-repo/-/blob/branch1/lab2.clab.yml?ref_type=heads",
+			repo: &GitLabRepo{
+				GitRepoStruct{
+					ProjectOwner:   "rdodin",
+					RepositoryName: "clab-test-repo",
+					CloneURL:       urlFromStr("https://gitlab.com/rdodin/clab-test-repo"),
+					GitBranch:      "branch1",
+					FileName:       "lab2.clab.yml",
+				},
+			},
+			wantErr: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			repo, err := NewGitLabRepoFromURL(urlFromStr(tt.url))
+
+			if (err != nil) != tt.wantErr {
+				t.Errorf("ParseGitURL() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+
+			if (err != nil) && errors.Is(err, errInvalidURL) {
+				return
+			}
+
+			// when we do not manipulate url path in the ParseGitURL method
+			// the wantRepo.URL field will be nil, so we need to set it up
+			// to match the original URL
+			if tt.repo.URL == nil {
+				tt.repo.URL = urlFromStr(tt.url)
+			}
+
+			// when we do not manipulate CloneURL in the ParseGitURL method
+			// the wantRepo.CloneURL field will be nil, so we need to set it up
+			// to match the original URL
+			if tt.repo.CloneURL == nil {
+				tt.repo.CloneURL = urlFromStr(tt.url)
+			}
+
+			if diff := cmp.Diff(repo, tt.repo); diff != "" {
+				t.Errorf("TestNewGitLabRepoFromURL() mismatch:\n%s", diff)
+			}
+		})
+	}
+}
+
+func TestIsGitLabURL(t *testing.T) {
+	tests := []struct {
+		name  string
+		input string
+		want  bool
+	}{
+		{
+			name:  "gitlab.com",
+			input: "http://gitlab.com",
+			want:  true,
+		},
+		{
+			name:  "gitlab.com/containers/containerlab/blob/master/README.md",
+			input: "https://gitlab.com/containers/containerlab/blob/master/README.md",
+			want:  true,
+		},
+		{
+			name:  "google.com/containers",
+			input: "http://google.com/containers",
+			want:  false,
+		},
+		{
+			name:  "google.com/containers/containerlab/blob/master/README.md",
+			input: "https://google.com/containers/containerlab/blob/master/README.md",
+			want:  false,
+		},
+		{
+			name:  "github.com/containers",
+			input: "http://github.com/containers",
+			want:  false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if output := IsGitLabURL(urlFromStr(tt.input)); output != tt.want {
+				t.Errorf("Test %q failed: want %v, but got %v", tt.name, tt.want, output)
+			}
+		})
+	}
+}
diff --git a/tests/01-smoke/12-cloned-lab.robot b/tests/01-smoke/12-cloned-lab.robot
index 793ab879..1f8c9403 100644
--- a/tests/01-smoke/12-cloned-lab.robot
+++ b/tests/01-smoke/12-cloned-lab.robot
@@ -7,14 +7,19 @@ Suite Teardown      Cleanup
 
 
 *** Variables ***
-${lab1-url}     https://github.com/hellt/clab-test-repo
-${lab1-url2}    https://github.com/hellt/clab-test-repo/blob/main/lab1.clab.yml
-${lab2-url}     https://github.com/hellt/clab-test-repo/tree/branch1
-${runtime}      docker
+${lab1-url}             https://github.com/hellt/clab-test-repo
+${lab1-shorturl}        hellt/clab-test-repo
+${lab1-url2}            https://github.com/hellt/clab-test-repo/blob/main/lab1.clab.yml
+${lab2-url}             https://github.com/hellt/clab-test-repo/tree/branch1
+
+${lab1-gitlab-url}      https://github.com/hellt/clab-test-repo
+${lab1-gitlab-url2}     https://github.com/hellt/clab-test-repo/blob/main/lab1.clab.yml
+${lab2-gitlab-url}      https://github.com/hellt/clab-test-repo/tree/branch1
+${runtime}              docker
 
 
 *** Test Cases ***
-Test lab1
+Test lab1 with Github
     ${output} =    Process.Run Process
     ...    sudo -E ${CLAB_BIN} --runtime ${runtime} deploy -t ${lab1-url}
     ...    shell=True
@@ -29,7 +34,22 @@ Test lab1
 
     Cleanup
 
-Test lab2
+Test lab1 with Gitlab
+    ${output} =    Process.Run Process
+    ...    sudo -E ${CLAB_BIN} --runtime ${runtime} deploy -t ${lab1-gitlab-url}
+    ...    shell=True
+
+    Log    ${output.stdout}
+    Log    ${output.stderr}
+
+    Should Be Equal As Integers    ${output.rc}    0
+
+    # check that node3 was filtered and not present in the lab output
+    Should Contain    ${output.stdout}    clab-lab1-node1
+
+    Cleanup
+
+Test lab2 with Github
     ${output} =    Process.Run Process
     ...    sudo -E ${CLAB_BIN} --runtime ${runtime} deploy -t ${lab1-url2}
     ...    shell=True
@@ -44,7 +64,22 @@ Test lab2
 
     Cleanup
 
-Test lab3
+Test lab2 with Gitlab
+    ${output} =    Process.Run Process
+    ...    sudo -E ${CLAB_BIN} --runtime ${runtime} deploy -t ${lab1-gitlab-url2}
+    ...    shell=True
+
+    Log    ${output.stdout}
+    Log    ${output.stderr}
+
+    Should Be Equal As Integers    ${output.rc}    0
+
+    # check that node3 was filtered and not present in the lab output
+    Should Contain    ${output.stdout}    clab-lab1-node1
+
+    Cleanup
+
+Test lab3 with Github
     ${output} =    Process.Run Process
     ...    sudo -E ${CLAB_BIN} --runtime ${runtime} deploy -t ${lab2-url}
     ...    shell=True
@@ -57,6 +92,38 @@ Test lab3
     # check that node3 was filtered and not present in the lab output
     Should Contain    ${output.stdout}    clab-lab2-node1
 
+    Cleanup
+
+Test lab3 with Gitlab
+    ${output} =    Process.Run Process
+    ...    sudo -E ${CLAB_BIN} --runtime ${runtime} deploy -t ${lab2-gitlab-url}
+    ...    shell=True
+
+    Log    ${output.stdout}
+    Log    ${output.stderr}
+
+    Should Be Equal As Integers    ${output.rc}    0
+
+    # check that node3 was filtered and not present in the lab output
+    Should Contain    ${output.stdout}    clab-lab2-node1
+
+    Cleanup
+
+Test lab1 with short github url
+    ${output} =    Process.Run Process
+    ...    sudo -E ${CLAB_BIN} --runtime ${runtime} deploy -t ${lab1-shorturl}
+    ...    shell=True
+
+    Log    ${output.stdout}
+    Log    ${output.stderr}
+
+    Should Be Equal As Integers    ${output.rc}    0
+
+    # check that node3 was filtered and not present in the lab output
+    Should Contain    ${output.stdout}    clab-lab1-node1
+
+    Cleanup
+
 
 *** Keywords ***
 Cleanup
diff --git a/tests/rf-run.sh b/tests/rf-run.sh
index d7171a13..3e327323 100755
--- a/tests/rf-run.sh
+++ b/tests/rf-run.sh
@@ -15,7 +15,7 @@ fi
 
 echo "Running tests with containerlab binary at $(which ${CLAB_BIN}) path and selected runtime: $1"
 
-COV_DIR=tests/coverage
+COV_DIR=/tmp/clab-tests/coverage
 
 # coverage output directory
 mkdir -p ${COV_DIR}
diff --git a/utils/file_test.go b/utils/file_test.go
index 1dbd4b58..da891a5a 100644
--- a/utils/file_test.go
+++ b/utils/file_test.go
@@ -84,3 +84,36 @@ func TestFileLines(t *testing.T) {
 		})
 	}
 }
+
+func TestIsGitHubShortURL(t *testing.T) {
+	tests := []struct {
+		name string
+		url  string
+		want bool
+	}{
+		{
+			name: "Valid Short URL",
+			url:  "user/repo",
+			want: true,
+		},
+		{
+			name: "Invalid Short URL - More than one slash",
+			url:  "user/repo/extra",
+			want: false,
+		},
+		{
+			name: "Invalid Short URL - Starts with http",
+			url:  "http://user/repo",
+			want: false,
+		},
+		// Add more test cases as needed
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := IsGitHubShortURL(tt.url); got != tt.want {
+				t.Errorf("IsGitHubShortURL() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/utils/http_test.go b/utils/http_test.go
deleted file mode 100644
index af9ca1b9..00000000
--- a/utils/http_test.go
+++ /dev/null
@@ -1,193 +0,0 @@
-package utils
-
-import (
-	"errors"
-	"testing"
-
-	"github.com/google/go-cmp/cmp"
-)
-
-func TestIsGitHubURL(t *testing.T) {
-	tests := []struct {
-		name  string
-		input string
-		want  bool
-	}{
-		{
-			name:  "github.com",
-			input: "github.com",
-			want:  true,
-		},
-		{
-			name:  "github.com/containers/containerlab/blob/master/README.md",
-			input: "github.com/containers/containerlab/blob/master/README.md",
-			want:  true,
-		},
-		{
-			name:  "google.com/containers",
-			input: "google.com/containers",
-			want:  false,
-		},
-		{
-			name:  "google.com/containers/containerlab/blob/master/README.md",
-			input: "google.com/containers/containerlab/blob/master/README.md",
-			want:  false,
-		},
-		{
-			name:  "gitlab.com/containers",
-			input: "gitlab.com/containers",
-			want:  false,
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			if output := IsGitHubURL(tt.input); output != tt.want {
-				t.Errorf("Test %q failed: want %v, but got %v", tt.name, tt.want, output)
-			}
-		})
-	}
-}
-
-func TestGithubURLParse(t *testing.T) {
-	tests := []struct {
-		name           string
-		ghURL          string
-		expectedResult *GithubURL
-		expectedError  error
-	}{
-		{
-			name:  "bare github url without trailing slash",
-			ghURL: "https://github.com/srl-labs/repo-name",
-			expectedResult: &GithubURL{
-				URLBase:        "https://github.com",
-				ProjectOwner:   "srl-labs",
-				RepositoryName: "repo-name",
-			},
-			expectedError: nil,
-		},
-		{
-			name:  "bare github url with trailing slash",
-			ghURL: "https://github.com/srl-labs/repo-name/",
-			expectedResult: &GithubURL{
-				URLBase:        "https://github.com",
-				ProjectOwner:   "srl-labs",
-				RepositoryName: "repo-name",
-			},
-			expectedError: nil,
-		},
-		{
-			name:  "bare github.dev url with trailing slash",
-			ghURL: "https://github.dev/srl-labs/repo-name/",
-			expectedResult: &GithubURL{
-				URLBase:        "https://github.com",
-				ProjectOwner:   "srl-labs",
-				RepositoryName: "repo-name",
-			},
-			expectedError: nil,
-		},
-		{
-			name:  "bare github url with .git suffix",
-			ghURL: "https://github.com/srl-labs/repo-name.git",
-			expectedResult: &GithubURL{
-				URLBase:        "https://github.com",
-				ProjectOwner:   "srl-labs",
-				RepositoryName: "repo-name",
-			},
-			expectedError: nil,
-		},
-		{
-			name:           "invalid url with just org name",
-			ghURL:          "https://github.com/srl-labs/",
-			expectedResult: &GithubURL{},
-			expectedError:  errInvalidGithubURL,
-		},
-		{
-			name:           "invalid url with no owner and no org",
-			ghURL:          "https://github.com/",
-			expectedResult: &GithubURL{},
-			expectedError:  errInvalidGithubURL,
-		},
-		{
-			name:  "github url with a clab file on the main branch",
-			ghURL: "https://github.com/srl-labs/repo-name/blob/main/file.clab.yml",
-			expectedResult: &GithubURL{
-				URLBase:        "https://github.com",
-				ProjectOwner:   "srl-labs",
-				RepositoryName: "repo-name",
-				GitBranch:      "main",
-				FileName:       "file.clab.yml",
-			},
-			expectedError: nil,
-		},
-		{
-			name:  "github url with a yaml file on the main branch",
-			ghURL: "https://github.com/srl-labs/repo-name/blob/main/file.yaml",
-			expectedResult: &GithubURL{
-				URLBase:        "https://github.com",
-				ProjectOwner:   "srl-labs",
-				RepositoryName: "repo-name",
-				GitBranch:      "main",
-				FileName:       "file.yaml",
-			},
-			expectedError: nil,
-		},
-		{
-			name:           "utl with invalid file on the main branch",
-			ghURL:          "https://github.com/srl-labs/repo-name/blob/main/file.foo",
-			expectedResult: &GithubURL{},
-			expectedError:  errInvalidGithubURL,
-		},
-		{
-			name:  "github url with a specified git ref and no file",
-			ghURL: "https://github.com/srl-labs/repo-name/tree/some-branch",
-			expectedResult: &GithubURL{
-				URLBase:        "https://github.com",
-				ProjectOwner:   "srl-labs",
-				RepositoryName: "repo-name",
-				GitBranch:      "some-branch",
-			},
-			expectedError: nil,
-		},
-		{
-			name:  "github url with a specified git ref and no file and trailing slash",
-			ghURL: "https://github.com/srl-labs/repo-name/tree/some-branch/",
-			expectedResult: &GithubURL{
-				URLBase:        "https://github.com",
-				ProjectOwner:   "srl-labs",
-				RepositoryName: "repo-name",
-				GitBranch:      "some-branch",
-			},
-			expectedError: nil,
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			u := NewGithubURL()
-			err := u.Parse(tt.ghURL)
-
-			if err != nil && tt.expectedError == nil {
-				t.Fatalf("unexpected error: %v", err)
-			}
-
-			if err == nil && tt.expectedError != nil {
-				t.Errorf("expected error: %v, but got nil", tt.expectedError)
-			}
-
-			if err != nil && tt.expectedError != nil {
-				if !errors.Is(err, tt.expectedError) {
-					t.Fatalf("expected error: %v, but got %v", err, tt.expectedError)
-				}
-				// exit the test case as we don't want to compare url structs
-				// since when error is available and matches the expected error
-				// we don't care about the state the struct is in
-				return
-			}
-
-			if diff := cmp.Diff(u, tt.expectedResult); diff != "" {
-				t.Errorf("got result: = %v, expected %v, diff:\n%s", u, tt.expectedResult, diff)
-			}
-		})
-	}
-}