netboxlabs-netbox-branching 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- netboxlabs_netbox_branching-0.3.0/LICENSE.md +164 -0
- netboxlabs_netbox_branching-0.3.0/PKG-INFO +87 -0
- netboxlabs_netbox_branching-0.3.0/README.md +56 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/__init__.py +24 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/api/__init__.py +0 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/api/serializers.py +138 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/api/urls.py +10 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/api/views.py +125 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/choices.py +46 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/constants.py +11 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/contextvars.py +8 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/database.py +26 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/events.py +26 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/filtersets.py +94 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/forms/__init__.py +5 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/forms/bulk_edit.py +26 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/forms/bulk_import.py +16 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/forms/filtersets.py +52 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/forms/misc.py +38 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/forms/model_forms.py +20 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/jobs.py +133 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/middleware.py +72 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/migrations/0001_initial.py +106 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/migrations/0002_branch_schema_id_unique.py +16 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/migrations/__init__.py +0 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/models/__init__.py +2 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/models/branches.py +511 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/models/changes.py +271 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/navigation.py +24 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/search.py +13 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/signal_receivers.py +123 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/signals.py +26 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/tables/__init__.py +2 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/tables/columns.py +88 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/tables/tables.py +174 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/template_content.py +43 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/templates/netbox_branching/branch.html +222 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/templates/netbox_branching/branch_action.html +57 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/templates/netbox_branching/inc/branch_selector.html +35 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/templates/netbox_branching/inc/modified_notice.html +10 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/urls.py +17 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/utilities.py +165 -0
- netboxlabs_netbox_branching-0.3.0/netbox_branching/views.py +294 -0
- netboxlabs_netbox_branching-0.3.0/netboxlabs_netbox_branching.egg-info/PKG-INFO +87 -0
- netboxlabs_netbox_branching-0.3.0/netboxlabs_netbox_branching.egg-info/SOURCES.txt +48 -0
- netboxlabs_netbox_branching-0.3.0/netboxlabs_netbox_branching.egg-info/dependency_links.txt +1 -0
- netboxlabs_netbox_branching-0.3.0/netboxlabs_netbox_branching.egg-info/requires.txt +13 -0
- netboxlabs_netbox_branching-0.3.0/netboxlabs_netbox_branching.egg-info/top_level.txt +1 -0
- netboxlabs_netbox_branching-0.3.0/pyproject.toml +69 -0
- netboxlabs_netbox_branching-0.3.0/setup.cfg +4 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# PolyForm Shield License 1.0.0
|
|
2
|
+
|
|
3
|
+
<https://polyformproject.org/licenses/shield/1.0.0>
|
|
4
|
+
|
|
5
|
+
## Acceptance
|
|
6
|
+
|
|
7
|
+
In order to get any license under these terms, you must agree
|
|
8
|
+
to them as both strict obligations and conditions to all
|
|
9
|
+
your licenses.
|
|
10
|
+
|
|
11
|
+
## Copyright License
|
|
12
|
+
|
|
13
|
+
The licensor grants you a copyright license for the
|
|
14
|
+
software to do everything you might do with the software
|
|
15
|
+
that would otherwise infringe the licensor's copyright
|
|
16
|
+
in it for any permitted purpose. However, you may
|
|
17
|
+
only distribute the software according to [Distribution
|
|
18
|
+
License](#distribution-license) and make changes or new works
|
|
19
|
+
based on the software according to [Changes and New Works
|
|
20
|
+
License](#changes-and-new-works-license).
|
|
21
|
+
|
|
22
|
+
## Distribution License
|
|
23
|
+
|
|
24
|
+
The licensor grants you an additional copyright license
|
|
25
|
+
to distribute copies of the software. Your license
|
|
26
|
+
to distribute covers distributing the software with
|
|
27
|
+
changes and new works permitted by [Changes and New Works
|
|
28
|
+
License](#changes-and-new-works-license).
|
|
29
|
+
|
|
30
|
+
## Notices
|
|
31
|
+
|
|
32
|
+
You must ensure that anyone who gets a copy of any part of
|
|
33
|
+
the software from you also gets a copy of these terms or the
|
|
34
|
+
URL for them above, as well as copies of any plain-text lines
|
|
35
|
+
beginning with `Required Notice:` that the licensor provided
|
|
36
|
+
with the software. For example:
|
|
37
|
+
|
|
38
|
+
> Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
|
|
39
|
+
|
|
40
|
+
## Changes and New Works License
|
|
41
|
+
|
|
42
|
+
The licensor grants you an additional copyright license to
|
|
43
|
+
make changes and new works based on the software for any
|
|
44
|
+
permitted purpose.
|
|
45
|
+
|
|
46
|
+
## Patent License
|
|
47
|
+
|
|
48
|
+
The licensor grants you a patent license for the software that
|
|
49
|
+
covers patent claims the licensor can license, or becomes able
|
|
50
|
+
to license, that you would infringe by using the software.
|
|
51
|
+
|
|
52
|
+
## Noncompete
|
|
53
|
+
|
|
54
|
+
Any purpose is a permitted purpose, except for providing any
|
|
55
|
+
product that competes with the software or any product the
|
|
56
|
+
licensor or any of its affiliates provides using the software.
|
|
57
|
+
|
|
58
|
+
## Competition
|
|
59
|
+
|
|
60
|
+
Goods and services compete even when they provide functionality
|
|
61
|
+
through different kinds of interfaces or for different technical
|
|
62
|
+
platforms. Applications can compete with services, libraries
|
|
63
|
+
with plugins, frameworks with development tools, and so on,
|
|
64
|
+
even if they're written in different programming languages
|
|
65
|
+
or for different computer architectures. Goods and services
|
|
66
|
+
compete even when provided free of charge. If you market a
|
|
67
|
+
product as a practical substitute for the software or another
|
|
68
|
+
product, it definitely competes.
|
|
69
|
+
|
|
70
|
+
## New Products
|
|
71
|
+
|
|
72
|
+
If you are using the software to provide a product that does
|
|
73
|
+
not compete, but the licensor or any of its affiliates brings
|
|
74
|
+
your product into competition by providing a new version of
|
|
75
|
+
the software or another product using the software, you may
|
|
76
|
+
continue using versions of the software available under these
|
|
77
|
+
terms beforehand to provide your competing product, but not
|
|
78
|
+
any later versions.
|
|
79
|
+
|
|
80
|
+
## Discontinued Products
|
|
81
|
+
|
|
82
|
+
You may begin using the software to compete with a product
|
|
83
|
+
or service that the licensor or any of its affiliates has
|
|
84
|
+
stopped providing, unless the licensor includes a plain-text
|
|
85
|
+
line beginning with `Licensor Line of Business:` with the
|
|
86
|
+
software that mentions that line of business. For example:
|
|
87
|
+
|
|
88
|
+
> Licensor Line of Business: YoyodyneCMS Content Management
|
|
89
|
+
System (http://example.com/cms)
|
|
90
|
+
|
|
91
|
+
## Sales of Business
|
|
92
|
+
|
|
93
|
+
If the licensor or any of its affiliates sells a line of
|
|
94
|
+
business developing the software or using the software
|
|
95
|
+
to provide a product, the buyer can also enforce
|
|
96
|
+
[Noncompete](#noncompete) for that product.
|
|
97
|
+
|
|
98
|
+
## Fair Use
|
|
99
|
+
|
|
100
|
+
You may have "fair use" rights for the software under the
|
|
101
|
+
law. These terms do not limit them.
|
|
102
|
+
|
|
103
|
+
## No Other Rights
|
|
104
|
+
|
|
105
|
+
These terms do not allow you to sublicense or transfer any of
|
|
106
|
+
your licenses to anyone else, or prevent the licensor from
|
|
107
|
+
granting licenses to anyone else. These terms do not imply
|
|
108
|
+
any other licenses.
|
|
109
|
+
|
|
110
|
+
## Patent Defense
|
|
111
|
+
|
|
112
|
+
If you make any written claim that the software infringes or
|
|
113
|
+
contributes to infringement of any patent, your patent license
|
|
114
|
+
for the software granted under these terms ends immediately. If
|
|
115
|
+
your company makes such a claim, your patent license ends
|
|
116
|
+
immediately for work on behalf of your company.
|
|
117
|
+
|
|
118
|
+
## Violations
|
|
119
|
+
|
|
120
|
+
The first time you are notified in writing that you have
|
|
121
|
+
violated any of these terms, or done anything with the software
|
|
122
|
+
not covered by your licenses, your licenses can nonetheless
|
|
123
|
+
continue if you come into full compliance with these terms,
|
|
124
|
+
and take practical steps to correct past violations, within
|
|
125
|
+
32 days of receiving notice. Otherwise, all your licenses
|
|
126
|
+
end immediately.
|
|
127
|
+
|
|
128
|
+
## No Liability
|
|
129
|
+
|
|
130
|
+
***As far as the law allows, the software comes as is, without
|
|
131
|
+
any warranty or condition, and the licensor will not be liable
|
|
132
|
+
to you for any damages arising out of these terms or the use
|
|
133
|
+
or nature of the software, under any kind of legal claim.***
|
|
134
|
+
|
|
135
|
+
## Definitions
|
|
136
|
+
|
|
137
|
+
The **licensor** is the individual or entity offering these
|
|
138
|
+
terms, and the **software** is the software the licensor makes
|
|
139
|
+
available under these terms.
|
|
140
|
+
|
|
141
|
+
A **product** can be a good or service, or a combination
|
|
142
|
+
of them.
|
|
143
|
+
|
|
144
|
+
**You** refers to the individual or entity agreeing to these
|
|
145
|
+
terms.
|
|
146
|
+
|
|
147
|
+
**Your company** is any legal entity, sole proprietorship,
|
|
148
|
+
or other kind of organization that you work for, plus all
|
|
149
|
+
its affiliates.
|
|
150
|
+
|
|
151
|
+
**Affiliates** means the other organizations than an
|
|
152
|
+
organization has control over, is under the control of, or is
|
|
153
|
+
under common control with.
|
|
154
|
+
|
|
155
|
+
**Control** means ownership of substantially all the assets of
|
|
156
|
+
an entity, or the power to direct its management and policies
|
|
157
|
+
by vote, contract, or otherwise. Control can be direct or
|
|
158
|
+
indirect.
|
|
159
|
+
|
|
160
|
+
**Your licenses** are all the licenses granted to you for the
|
|
161
|
+
software under these terms.
|
|
162
|
+
|
|
163
|
+
**Use** means anything you do with the software requiring one
|
|
164
|
+
of your licenses.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: netboxlabs-netbox-branching
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: A git-like branching implementation for NetBox
|
|
5
|
+
Author-email: NetBox Labs <support@netboxlabs.com>
|
|
6
|
+
Maintainer-email: NetBox Labs <support@netboxlabs.com>
|
|
7
|
+
License: PolyForm Shield License 1.0.0
|
|
8
|
+
Project-URL: Homepage, https://netboxlabs.com/
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE.md
|
|
20
|
+
Requires-Dist: Django
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: black; extra == "dev"
|
|
23
|
+
Requires-Dist: check-manifest; extra == "dev"
|
|
24
|
+
Requires-Dist: ruff; extra == "dev"
|
|
25
|
+
Requires-Dist: mkdocs; extra == "dev"
|
|
26
|
+
Requires-Dist: mkdocs-material; extra == "dev"
|
|
27
|
+
Provides-Extra: test
|
|
28
|
+
Requires-Dist: coverage; extra == "test"
|
|
29
|
+
Requires-Dist: pytest; extra == "test"
|
|
30
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
31
|
+
|
|
32
|
+
# NetBox Branching
|
|
33
|
+
|
|
34
|
+
This [NetBox](http://netboxlabs.com/oss/netbox/) plugin introduces branching functionality. A branch is a discrete, static snapshot of the NetBox database which can be modified independently and later merged back into the main database. This enables users to make "offline" changes to objects within NetBox and avoid interfering with its integrity as the network source of truth. It also provides the opportunity to review changes in bulk prior to their application.
|
|
35
|
+
|
|
36
|
+
## Requirements
|
|
37
|
+
|
|
38
|
+
* NetBox v4.1 or later
|
|
39
|
+
* PostgreSQL 12 or later
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
Brief installation instructions are provided below. For a complete installation guide, please refer to the included documentation.
|
|
44
|
+
|
|
45
|
+
1. Activate the NetBox virtual environment:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
$ source /opt/netbox/venv/bin/activate
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
2. Install the plugin from [PyPI](https://pypi.org/project/netboxlabs-netbox-branching/):
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
$ pip install netboxlabs-netbox-branching
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
3. Add `netbox_branching` to `PLUGINS` in `configuration.py`:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
PLUGINS = [
|
|
61
|
+
# ...
|
|
62
|
+
'netbox_branching',
|
|
63
|
+
]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
4. Create `local_settings.py` to override the `DATABASES` & `DATABASE_ROUTERS` settings. This enables dynamic schema support.
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from netbox_branching.utilities import DynamicSchemaDict
|
|
70
|
+
from .configuration import DATABASE
|
|
71
|
+
|
|
72
|
+
# Wrap DATABASES with DynamicSchemaDict for dynamic schema support
|
|
73
|
+
DATABASES = DynamicSchemaDict({
|
|
74
|
+
'default': DATABASE,
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
# Employ our custom database router
|
|
78
|
+
DATABASE_ROUTERS = [
|
|
79
|
+
'netbox_branching.database.BranchAwareRouter',
|
|
80
|
+
]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
5. Run NetBox migrations:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
$ ./manage.py migrate
|
|
87
|
+
```
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# NetBox Branching
|
|
2
|
+
|
|
3
|
+
This [NetBox](http://netboxlabs.com/oss/netbox/) plugin introduces branching functionality. A branch is a discrete, static snapshot of the NetBox database which can be modified independently and later merged back into the main database. This enables users to make "offline" changes to objects within NetBox and avoid interfering with its integrity as the network source of truth. It also provides the opportunity to review changes in bulk prior to their application.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
* NetBox v4.1 or later
|
|
8
|
+
* PostgreSQL 12 or later
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
Brief installation instructions are provided below. For a complete installation guide, please refer to the included documentation.
|
|
13
|
+
|
|
14
|
+
1. Activate the NetBox virtual environment:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
$ source /opt/netbox/venv/bin/activate
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
2. Install the plugin from [PyPI](https://pypi.org/project/netboxlabs-netbox-branching/):
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
$ pip install netboxlabs-netbox-branching
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
3. Add `netbox_branching` to `PLUGINS` in `configuration.py`:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
PLUGINS = [
|
|
30
|
+
# ...
|
|
31
|
+
'netbox_branching',
|
|
32
|
+
]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
4. Create `local_settings.py` to override the `DATABASES` & `DATABASE_ROUTERS` settings. This enables dynamic schema support.
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from netbox_branching.utilities import DynamicSchemaDict
|
|
39
|
+
from .configuration import DATABASE
|
|
40
|
+
|
|
41
|
+
# Wrap DATABASES with DynamicSchemaDict for dynamic schema support
|
|
42
|
+
DATABASES = DynamicSchemaDict({
|
|
43
|
+
'default': DATABASE,
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
# Employ our custom database router
|
|
47
|
+
DATABASE_ROUTERS = [
|
|
48
|
+
'netbox_branching.database.BranchAwareRouter',
|
|
49
|
+
]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
5. Run NetBox migrations:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
$ ./manage.py migrate
|
|
56
|
+
```
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from netbox.plugins import PluginConfig
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AppConfig(PluginConfig):
|
|
5
|
+
name = 'netbox_branching'
|
|
6
|
+
verbose_name = 'NetBox Branching'
|
|
7
|
+
description = 'A git-like branching implementation for NetBox'
|
|
8
|
+
version = '0.3.0'
|
|
9
|
+
base_url = 'branching'
|
|
10
|
+
min_version = '4.1'
|
|
11
|
+
middleware = [
|
|
12
|
+
'netbox_branching.middleware.BranchMiddleware'
|
|
13
|
+
]
|
|
14
|
+
default_settings = {
|
|
15
|
+
# This string is prefixed to the name of each new branch schema during provisioning
|
|
16
|
+
'schema_prefix': 'branch_',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
def ready(self):
|
|
20
|
+
super().ready()
|
|
21
|
+
from . import events, search, signal_receivers
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
config = AppConfig
|
|
File without changes
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
from drf_spectacular.utils import extend_schema_field
|
|
2
|
+
from rest_framework import serializers
|
|
3
|
+
|
|
4
|
+
from core.choices import ObjectChangeActionChoices
|
|
5
|
+
from netbox.api.exceptions import SerializerNotFound
|
|
6
|
+
from netbox.api.fields import ChoiceField, ContentTypeField
|
|
7
|
+
from netbox.api.serializers import NetBoxModelSerializer
|
|
8
|
+
from netbox_branching.choices import BranchEventTypeChoices
|
|
9
|
+
from netbox_branching.models import ChangeDiff, Branch, BranchEvent
|
|
10
|
+
from users.api.serializers import UserSerializer
|
|
11
|
+
from utilities.api import get_serializer_for_model
|
|
12
|
+
|
|
13
|
+
__all__ = (
|
|
14
|
+
'BranchSerializer',
|
|
15
|
+
'BranchEventSerializer',
|
|
16
|
+
'ChangeDiffSerializer',
|
|
17
|
+
'CommitSerializer',
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BranchSerializer(NetBoxModelSerializer):
|
|
22
|
+
url = serializers.HyperlinkedIdentityField(
|
|
23
|
+
view_name='plugins-api:netbox_branching-api:branch-detail'
|
|
24
|
+
)
|
|
25
|
+
owner = UserSerializer(
|
|
26
|
+
nested=True,
|
|
27
|
+
read_only=True
|
|
28
|
+
)
|
|
29
|
+
merged_by = UserSerializer(
|
|
30
|
+
nested=True,
|
|
31
|
+
read_only=True
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
class Meta:
|
|
35
|
+
model = Branch
|
|
36
|
+
fields = [
|
|
37
|
+
'id', 'url', 'display', 'name', 'status', 'owner', 'description', 'schema_id', 'last_sync', 'merged_time',
|
|
38
|
+
'merged_by', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
|
39
|
+
]
|
|
40
|
+
brief_fields = ('id', 'url', 'display', 'name', 'status', 'description')
|
|
41
|
+
|
|
42
|
+
def create(self, validated_data):
|
|
43
|
+
"""
|
|
44
|
+
Record the user who created the Branch as its owner.
|
|
45
|
+
"""
|
|
46
|
+
validated_data['owner'] = self.context['request'].user
|
|
47
|
+
return super().create(validated_data)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class BranchEventSerializer(NetBoxModelSerializer):
|
|
51
|
+
url = serializers.HyperlinkedIdentityField(
|
|
52
|
+
view_name='plugins-api:netbox_branching-api:branchevent-detail'
|
|
53
|
+
)
|
|
54
|
+
branch = BranchSerializer(
|
|
55
|
+
nested=True,
|
|
56
|
+
read_only=True
|
|
57
|
+
)
|
|
58
|
+
user = UserSerializer(
|
|
59
|
+
nested=True,
|
|
60
|
+
read_only=True
|
|
61
|
+
)
|
|
62
|
+
type = ChoiceField(
|
|
63
|
+
choices=BranchEventTypeChoices,
|
|
64
|
+
read_only=True
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
class Meta:
|
|
68
|
+
model = BranchEvent
|
|
69
|
+
fields = [
|
|
70
|
+
'id', 'url', 'display', 'time', 'branch', 'user', 'type',
|
|
71
|
+
]
|
|
72
|
+
brief_fields = ('id', 'url', 'display')
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class ChangeDiffSerializer(NetBoxModelSerializer):
|
|
76
|
+
url = serializers.HyperlinkedIdentityField(
|
|
77
|
+
view_name='plugins-api:netbox_branching-api:changediff-detail'
|
|
78
|
+
)
|
|
79
|
+
branch = BranchSerializer(
|
|
80
|
+
nested=True,
|
|
81
|
+
read_only=True
|
|
82
|
+
)
|
|
83
|
+
object_type = ContentTypeField(
|
|
84
|
+
read_only=True
|
|
85
|
+
)
|
|
86
|
+
object = serializers.SerializerMethodField(
|
|
87
|
+
read_only=True
|
|
88
|
+
)
|
|
89
|
+
action = ChoiceField(
|
|
90
|
+
choices=ObjectChangeActionChoices,
|
|
91
|
+
read_only=True
|
|
92
|
+
)
|
|
93
|
+
diff = serializers.JSONField(
|
|
94
|
+
read_only=True
|
|
95
|
+
)
|
|
96
|
+
original_data = serializers.JSONField(
|
|
97
|
+
source='original',
|
|
98
|
+
read_only=True,
|
|
99
|
+
allow_null=True
|
|
100
|
+
)
|
|
101
|
+
modified_data = serializers.JSONField(
|
|
102
|
+
source='modified',
|
|
103
|
+
read_only=True,
|
|
104
|
+
allow_null=True
|
|
105
|
+
)
|
|
106
|
+
current_data = serializers.JSONField(
|
|
107
|
+
source='current',
|
|
108
|
+
read_only=True,
|
|
109
|
+
allow_null=True
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
class Meta:
|
|
113
|
+
model = ChangeDiff
|
|
114
|
+
fields = [
|
|
115
|
+
'id', 'url', 'display', 'branch', 'object_type', 'object_id', 'object', 'object_repr', 'action',
|
|
116
|
+
'conflicts', 'diff', 'original_data', 'modified_data', 'current_data', 'last_updated',
|
|
117
|
+
]
|
|
118
|
+
brief_fields = ('id', 'url', 'display', 'object_type', 'object_id', 'object_repr', 'action')
|
|
119
|
+
|
|
120
|
+
@extend_schema_field(serializers.JSONField(allow_null=True))
|
|
121
|
+
def get_object(self, obj):
|
|
122
|
+
"""
|
|
123
|
+
Serialize a nested representation of the changed object.
|
|
124
|
+
"""
|
|
125
|
+
if obj.object is None:
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
serializer = get_serializer_for_model(obj.object)
|
|
130
|
+
except SerializerNotFound:
|
|
131
|
+
return obj.object_repr
|
|
132
|
+
data = serializer(obj.object, nested=True, context={'request': self.context['request']}).data
|
|
133
|
+
|
|
134
|
+
return data
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class CommitSerializer(serializers.Serializer):
|
|
138
|
+
commit = serializers.BooleanField(required=False)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from netbox.api.routers import NetBoxRouter
|
|
2
|
+
from . import views
|
|
3
|
+
|
|
4
|
+
router = NetBoxRouter()
|
|
5
|
+
router.APIRootView = views.RootView
|
|
6
|
+
router.register('branches', views.BranchViewSet)
|
|
7
|
+
router.register('branch-events', views.BranchEventViewSet)
|
|
8
|
+
router.register('changes', views.ChangeDiffViewSet)
|
|
9
|
+
|
|
10
|
+
urlpatterns = router.urls
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
from django.core.exceptions import PermissionDenied
|
|
2
|
+
from django.http import HttpResponseBadRequest
|
|
3
|
+
from drf_spectacular.utils import extend_schema
|
|
4
|
+
from rest_framework.decorators import action
|
|
5
|
+
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin
|
|
6
|
+
from rest_framework.response import Response
|
|
7
|
+
from rest_framework.routers import APIRootView
|
|
8
|
+
from rest_framework.viewsets import ModelViewSet
|
|
9
|
+
|
|
10
|
+
from core.api.serializers import JobSerializer
|
|
11
|
+
from netbox.api.viewsets import BaseViewSet, NetBoxReadOnlyModelViewSet
|
|
12
|
+
from netbox_branching import filtersets
|
|
13
|
+
from netbox_branching.jobs import MergeBranchJob, RevertBranchJob, SyncBranchJob
|
|
14
|
+
from netbox_branching.models import Branch, BranchEvent, ChangeDiff
|
|
15
|
+
from . import serializers
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RootView(APIRootView):
|
|
19
|
+
def get_view_name(self):
|
|
20
|
+
return 'Branching'
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BranchViewSet(ModelViewSet):
|
|
24
|
+
queryset = Branch.objects.all()
|
|
25
|
+
serializer_class = serializers.BranchSerializer
|
|
26
|
+
filterset_class = filtersets.BranchFilterSet
|
|
27
|
+
|
|
28
|
+
@extend_schema(
|
|
29
|
+
methods=['post'],
|
|
30
|
+
request=serializers.CommitSerializer(),
|
|
31
|
+
responses={200: JobSerializer()},
|
|
32
|
+
)
|
|
33
|
+
@action(detail=True, methods=['post'])
|
|
34
|
+
def sync(self, request, pk):
|
|
35
|
+
"""
|
|
36
|
+
Enqueue a background job to synchronize a branch from main.
|
|
37
|
+
"""
|
|
38
|
+
if not request.user.has_perm('netbox_branching.sync_branch'):
|
|
39
|
+
raise PermissionDenied("This user does not have permission to sync branches.")
|
|
40
|
+
|
|
41
|
+
branch = self.get_object()
|
|
42
|
+
if not branch.ready:
|
|
43
|
+
return HttpResponseBadRequest("Branch is not ready to sync.")
|
|
44
|
+
|
|
45
|
+
serializer = serializers.CommitSerializer(data=request.data)
|
|
46
|
+
commit = serializer.validated_data['commit'] if serializer.is_valid() else False
|
|
47
|
+
|
|
48
|
+
# Enqueue a background job
|
|
49
|
+
job = SyncBranchJob.enqueue(
|
|
50
|
+
instance=branch,
|
|
51
|
+
user=request.user,
|
|
52
|
+
commit=commit
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
return Response(JobSerializer(job, context={'request': request}).data)
|
|
56
|
+
|
|
57
|
+
@extend_schema(
|
|
58
|
+
methods=['post'],
|
|
59
|
+
request=serializers.CommitSerializer(),
|
|
60
|
+
responses={200: JobSerializer()},
|
|
61
|
+
)
|
|
62
|
+
@action(detail=True, methods=['post'])
|
|
63
|
+
def merge(self, request, pk):
|
|
64
|
+
"""
|
|
65
|
+
Enqueue a background job to merge a branch.
|
|
66
|
+
"""
|
|
67
|
+
if not request.user.has_perm('netbox_branching.merge_branch'):
|
|
68
|
+
raise PermissionDenied("This user does not have permission to merge branches.")
|
|
69
|
+
|
|
70
|
+
branch = self.get_object()
|
|
71
|
+
if not branch.ready:
|
|
72
|
+
return HttpResponseBadRequest("Branch is not ready to merge.")
|
|
73
|
+
|
|
74
|
+
serializer = serializers.CommitSerializer(data=request.data)
|
|
75
|
+
commit = serializer.validated_data['commit'] if serializer.is_valid() else False
|
|
76
|
+
|
|
77
|
+
# Enqueue a background job
|
|
78
|
+
job = MergeBranchJob.enqueue(
|
|
79
|
+
instance=branch,
|
|
80
|
+
user=request.user,
|
|
81
|
+
commit=commit
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
return Response(JobSerializer(job, context={'request': request}).data)
|
|
85
|
+
|
|
86
|
+
@extend_schema(
|
|
87
|
+
methods=['post'],
|
|
88
|
+
request=serializers.CommitSerializer(),
|
|
89
|
+
responses={200: JobSerializer()},
|
|
90
|
+
)
|
|
91
|
+
@action(detail=True, methods=['post'])
|
|
92
|
+
def revert(self, request, pk):
|
|
93
|
+
"""
|
|
94
|
+
Enqueue a background job to revert a merged branch.
|
|
95
|
+
"""
|
|
96
|
+
if not request.user.has_perm('netbox_branching.revert_branch'):
|
|
97
|
+
raise PermissionDenied("This user does not have permission to revert branches.")
|
|
98
|
+
|
|
99
|
+
branch = self.get_object()
|
|
100
|
+
if not branch.merged:
|
|
101
|
+
return HttpResponseBadRequest("Only merged branches can be reverted.")
|
|
102
|
+
|
|
103
|
+
serializer = serializers.CommitSerializer(data=request.data)
|
|
104
|
+
commit = serializer.validated_data['commit'] if serializer.is_valid() else False
|
|
105
|
+
|
|
106
|
+
# Enqueue a background job
|
|
107
|
+
job = RevertBranchJob.enqueue(
|
|
108
|
+
instance=branch,
|
|
109
|
+
user=request.user,
|
|
110
|
+
commit=commit
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
return Response(JobSerializer(job, context={'request': request}).data)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class BranchEventViewSet(ListModelMixin, RetrieveModelMixin, BaseViewSet):
|
|
117
|
+
queryset = BranchEvent.objects.all()
|
|
118
|
+
serializer_class = serializers.BranchEventSerializer
|
|
119
|
+
filterset_class = filtersets.BranchEventFilterSet
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class ChangeDiffViewSet(NetBoxReadOnlyModelViewSet):
|
|
123
|
+
queryset = ChangeDiff.objects.all()
|
|
124
|
+
serializer_class = serializers.ChangeDiffSerializer
|
|
125
|
+
filterset_class = filtersets.ChangeDiffFilterSet
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from django.utils.translation import gettext_lazy as _
|
|
2
|
+
|
|
3
|
+
from utilities.choices import ChoiceSet
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BranchStatusChoices(ChoiceSet):
|
|
7
|
+
NEW = 'new'
|
|
8
|
+
PROVISIONING = 'provisioning'
|
|
9
|
+
READY = 'ready'
|
|
10
|
+
SYNCING = 'syncing'
|
|
11
|
+
MERGING = 'merging'
|
|
12
|
+
REVERTING = 'reverting'
|
|
13
|
+
MERGED = 'merged'
|
|
14
|
+
FAILED = 'failed'
|
|
15
|
+
|
|
16
|
+
CHOICES = (
|
|
17
|
+
(NEW, _('New'), 'cyan'),
|
|
18
|
+
(PROVISIONING, _('Provisioning'), 'orange'),
|
|
19
|
+
(READY, _('Ready'), 'green'),
|
|
20
|
+
(SYNCING, _('Syncing'), 'orange'),
|
|
21
|
+
(MERGING, _('Merging'), 'orange'),
|
|
22
|
+
(REVERTING, _('Reverting'), 'orange'),
|
|
23
|
+
(MERGED, _('Merged'), 'blue'),
|
|
24
|
+
(FAILED, _('Failed'), 'red'),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
TRANSITIONAL = (
|
|
28
|
+
PROVISIONING,
|
|
29
|
+
SYNCING,
|
|
30
|
+
MERGING,
|
|
31
|
+
REVERTING
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class BranchEventTypeChoices(ChoiceSet):
|
|
36
|
+
PROVISIONED = 'provisioned'
|
|
37
|
+
SYNCED = 'synced'
|
|
38
|
+
MERGED = 'merged'
|
|
39
|
+
REVERTED = 'reverted'
|
|
40
|
+
|
|
41
|
+
CHOICES = (
|
|
42
|
+
(PROVISIONED, _('Provisioned'), 'green'),
|
|
43
|
+
(SYNCED, _('Synced'), 'cyan'),
|
|
44
|
+
(MERGED, _('Merged'), 'blue'),
|
|
45
|
+
(REVERTED, _('Reverted'), 'orange'),
|
|
46
|
+
)
|