`pytos2` CE: Official Python Library for the [Tufin Orchestration Suite](https://tufin.com)
==============================================
`pytos2` "Community Edition" (CE) is the official Python library
for the Tufin Orchestration Suite. It is created, maintained by
and supported by the Tufin Professional Services Solutions team (PSS). It wraps the
[Official TOS API](https://forum.tufin.com/support/kc/latest/Content/Suite/4423.htm)
and provides idiomatic Python-level types and features.

`pytos2-ce` currently supports TufinOS3 and both TOS Classic and Aurora. The
implementation of Aurora-specific GraphQL features, as well as some of the
below-stated features are still under active development.

Installation
------------
[Git](https://git-scm.com) is a prerequisite for installation. Also, development using either
[`venv`](https://docs.python.org/3/library/venv.html#creating-virtual-environments)
or [Docker](https://docs.docker.com/get-docker/)
is recommended.

`pytos2` CE is currently *not* yet available on [PyPI](https://pypi.org). To install, use
the following [`pip`](https://pypi.org/project/pip/) command:

```bash
$ pip install git+https://gitlab.com/tufinps/pytos2-ce
```

Setup
-----
`pytos2` CE recommends pre-setting the following environment
variables while using this library, and they will be assumed to be
set from this point onward. They can either be set directly
in the environment using a method of your choice, or you can use [python-dotenv](https://saurabh-kumar.com/python-dotenv/). They are as follows:

- `TOS_HOSTNAME`: The hostname where your TOS installation lives.
  It is assumed to *not* be a split SecureTrack/SecureChange
  environment.
- `SCW_API_USERNAME`: The username of the SecureChange user that we
  will be utilizing for the following examples. Please ensure that
  this user exists and has appropriate permissions for whatever
  workflows/tickets will be used.
- `SCW_API_PASSSWORD`: The password of the SecureChange user that we
  will be utilizing for the following examples.

Usage
-----
First, load the environment variables from the [Setup section](#Setup) into your Python session:

```python
from os import getenv

TOS_HOSTNAME = getenv("TOS_HOSTNAME")
SCW_API_USERNAME = getenv("SCW_API_USERNAME")
SCW_API_PASSWORD = getenv("SCW_API_PASSWORD")
```

### SecureChange
The following example will walk a SecureChange `Ticket` belonging to a
hypothetical `Workflow` from start to finish using `pytos2` CE,
showcasing its most-commonly-used features.

First, set up the `Scw` instance. This class instance will be
responsible for all SecureChange interactions:

```python
from pytos2.securechange import Scw

scw = Scw(TOS_HOSTNAME, SCW_API_USERNAME, SCW_API_PASSWORD)
```

#### Step 1
The first step of the sample `Workflow` we will be using is
enpictured here:

![Example Ticket Step #1](doc/securechange-step-1.png)

Since our `Ticket` has ID #2335, let's fetch the `Ticket` by id
with the following:

```python
ticket = scw.get_ticket(2335)
# ticket = Ticket(subject='Test Ticket #355', id=2335, status=<TicketStatus.INPROGRESS: 'In Progress'>, comments=[], _current_step=Step(id=22979, name='Service Selection', redone=False, skipped=False))
```

With that being successful, let's move on to `Step` #2.

#### Step 2
![Example Ticket Step #2](doc/securechange-step-2.png)

Now that we have the ticket, let's copy the Hyperlink field
"Website" from `Step` 1 to `Step` 2. (Technically, we'll be moving
the hyperlink from the first `Task` of `Step` 1 to the first
`Task` of `Step` 2, but since this example isn't using dynamic
assignment, we'll assume going forward that "`Step` N" is
referring to the first `Task` of "`Step` N"). First, let's
retrieve the value of the field on `Step 1` by name:

```python
hyperlink = ticket.steps[0].last_task.get_field("Website")
# hyperlink = Hyperlink(name='Website', id=974324, xsi_type=<FieldXsiType.HYPERLINK: 'hyperlink'>, url='http://example.com/tufin')
```
Great! Now, let's copy it to our current step's "Website" field:

```python
current_hyperlink = ticket.last_task.get_field('Website')
current_hyperlink = Hyperlink(name='Website', id=974326, xsi_type=<FieldXsiType.HYPERLINK: 'hyperlink'>, url='http://')

current_hyperlink.url = hyperlink.url

ticket = ticket.save()
```

*N.B.:* We are making the result of the saved `Ticket` our new
working `Ticket`, as the prior `Ticket`-in-memory would be
invalidated at this point (since we just saved it; `Ticket`s do not currently update in-place).

If all has gone right, let's go back to SecureChange proper and
refresh our `Ticket`'s at `Step` 2:

![Example Ticket Step #2, Edit #1](doc/securechange-step-2-edit-1.png)

Excellent. Now let's also programatically fill out the "Services" and "Service Expiration" fields, and advance the `Ticket`:

```python
services = ticket.last_task.get_field("Services")
# services = MultipleSelection(name='Services', id=974327, xsi_type=<FieldXsiType.MULTIPLE_SELECTION: 'multiple_selection'>, selected_options=[], options=[Option(id=None, value='HTTP'), Option(id=None, value='FTP'), Option(id=None, value='SSH')])
services.selected_options.append(services.options[0])
services.selected_options.append(services.options[2])

from datetime import date
expiration = ticket.last_task.get_field("Service Expiration")
# expiration = Date(name='Service Expiration', id=974328, xsi_type=<FieldXsiType.DATE: 'date'>, _date=None)
expiration.date = date(2021, 12, 22)

ticket = ticket.advance()
```

Double-checking with SecureChange, we indeed see that `Step` 2 has
the corresponding fields updated, and that the `Ticket` has been
advanced to the final step, `Step` 3:

![Example Ticket Step #2, Edit #2](doc/securechange-step-2-edit-2.png)

#### Step 3
`Step` 3 is the "Approval" step. In it, we'll accept this Service Change Request with an appropriate reason and check the "terms and conditions" checkbox programatically.

![Example Ticket Step #3](doc/securechange-step-3.png)

```python
approval = ticket.last_task.get_field("Approve")
# approval = ApproveReject(name='Approve', id=974334, xsi_type=<FieldXsiType.APPROVE_REJECT: 'approve_reject'>, approved=None)
approval.approved = True
approval.reason = 'This service looks reasonable.'
```

Now, let's check the checkbox.

 `pytos2` CE offers more than one
way to access `Step` fields. We've already seen
`ticket.task.get_field`, but there's also the `ticket.task.fields` property:

```python
# ticket.last_task.fields = [ApproveReject(name='Approve', id=974334, xsi_type=<FieldXsiType.APPROVE_REJECT: 'approve_reject'>, approved=True),
 Checkbox(name='I understand and agree to the license and terms of conditions.', id=974335, xsi_type=<FieldXsiType.CHECKBOX: 'checkbox'>, value=False)]
```

Let's access and check the checkbox with this method:

```python
checkbox = ticket.last_task.fields[-1]
# checkbox = Checkbox(name='I understand and agree to the license and terms of conditions.', id=974335, xsi_type=<FieldXsiType.CHECKBOX: 'checkbox'>, value=False)
checkbox.value = True
```
In production environments, you'll often want to use
`ticket.task.get_field` instead of get-by-index because if fields are ever reordered, your indexing code
will be broken. And while its entirely possible for field names to
change, they do so less frequently - and when they are, name changes are
easier to deal with than abstract indexes.

Finally, let's close `ticket`. All we need to do is advance the ticket, and if it's on the last step, SecureChange will close it automatically:

```python
ticket = ticket.advance()
```

As expected, visiting that same `Ticket` in SecureChange will show both the changes we've made, and that the `Ticket` is indeed closed!:

![Example Ticket, Closed](doc/securechange-closed.png)

Development
-----------
`pytos2` CE is under active development by the Tufin PSS team. Bug
and feature requests can created by
[opening a new issue](https://gitlab.com/tufinps/pytos2-ce/-/issues/new).

The versioning strategy is [semver](https://semver.org).

Support
-------
For additional `pytos2-ce` support or inquiries, please visit the
[pytos category of our Developer Community](https://community.tufin.com/c/pytos/11).
For sales or other general-purpose inquiries please contact your account manager or visit
[our contact page](https://www.tufin.com/contact-us).
