Skip to content

Commit 370cab3

Browse files
authored
Merge pull request #355 from delphix/release
Release 3.1.0
2 parents 63f1e8a + 7e40f1f commit 370cab3

File tree

31 files changed

+1028
-32
lines changed

31 files changed

+1028
-32
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 3.0.0
2+
current_version = 3.1.0
33
commit = False
44
tag = False
55
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\.(?P<release>[a-z]+)(?P<dev>\d+))?

.github/workflows/publish-docs.yml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ on:
99
- 'docs/**'
1010
paths:
1111
- 'docs/**'
12+
- '.github/workflows/publish-docs.yml'
1213

1314
jobs:
1415
publish:
@@ -17,6 +18,7 @@ jobs:
1718
matrix:
1819
python-version: [3.7]
1920
package: [docs]
21+
repository: ['delphix/virtualization-sdk']
2022

2123
steps:
2224
- uses: actions/checkout@v2
@@ -69,7 +71,18 @@ jobs:
6971
working-directory: ${{ matrix.package }}
7072
run: |
7173
pipenv run mkdocs build --clean
72-
- name: Deploy the contents of docs/site to gh-pages 🚀
74+
- name: Deploy the contents of docs/site to gh-pages (developer.delphix.com) 🚀
75+
if: ${{ github.repository == matrix.repository }}
76+
uses: peaceiris/actions-gh-pages@v3
77+
with:
78+
github_token: ${{ secrets.GITHUB_TOKEN }}
79+
publish_dir: docs/site
80+
commit_message: Deploy to gh-pages 🚀
81+
user_name: "github-actions[bot]"
82+
user_email: "github-actions[bot]@users.noreply.github.com"
83+
cname: developer.delphix.com
84+
- name: Deploy the contents of docs/site to personal gh-pages 🚀
85+
if: ${{ github.repository != matrix.repository }}
7386
uses: peaceiris/actions-gh-pages@v3
7487
with:
7588
github_token: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/publish-python-packages.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ on:
66
branches:
77
- master
88
- develop
9-
- 'release/**'
9+
- release
1010
paths:
1111
- 'dvp/src/main/python/dlpx/virtualization/VERSION'
12+
- '.github/workflows/publish-python-packages.yml'
1213

1314
jobs:
1415
publish:
@@ -39,4 +40,3 @@ jobs:
3940
run: |
4041
python setup.py sdist bdist_wheel
4142
twine upload dist/*
42-

common/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
bump2version==0.5.11
22
contextlib2==0.6.0.post1 ; python_version < '3'
3+
enum34==1.1.6
34
funcsigs==1.0.2 ; python_version < '3.0'
45
importlib-metadata==0.23 ; python_version < '3.8'
56
more-itertools==5.0.0 ; python_version <= '2.7'

common/setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
PYTHON_SRC = 'src/main/python'
55

66
install_requires = [
7-
"dvp-api == 1.4.0",
7+
"dvp-api == 1.5.0",
88
]
99

1010
with open(os.path.join(PYTHON_SRC, 'dlpx/virtualization/common/VERSION')) as version_file:
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.0.0
1+
3.1.0

common/src/main/python/dlpx/virtualization/common/_common_classes.py

Lines changed: 125 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
# Copyright (c) 2019 by Delphix. All rights reserved.
33
#
44

5-
from dlpx.virtualization.api import common_pb2
5+
from abc import ABCMeta
6+
from dlpx.virtualization.api import common_pb2, libs_pb2
67
from dlpx.virtualization.common.exceptions import IncorrectTypeError
8+
from enum import IntEnum
79

810
"""Classes used for Plugin Operations
911
@@ -17,7 +19,10 @@
1719
"RemoteConnection",
1820
"RemoteEnvironment",
1921
"RemoteHost",
20-
"RemoteUser"]
22+
"RemoteUser",
23+
"Credentials",
24+
"PasswordCredentials",
25+
"KeyPairCredentials"]
2126

2227

2328
class RemoteConnection(object):
@@ -293,3 +298,121 @@ def from_proto(user):
293298
common_pb2.RemoteUser)
294299
remote_user = RemoteUser(name=user.name, reference=user.reference)
295300
return remote_user
301+
302+
303+
class Credentials(object):
304+
"""Plugin base class for CredentialsResult to be used for plugin operations
305+
and library functions.
306+
307+
Plugin authors should use this instead of the corresponding protobuf generated
308+
class.
309+
310+
Args:
311+
username: User name.
312+
"""
313+
def __init__(self, username):
314+
if not isinstance(username, basestring):
315+
raise IncorrectTypeError(
316+
Credentials,
317+
'username',
318+
type(username),
319+
basestring)
320+
self.__username = username
321+
322+
__metaclass__ = ABCMeta
323+
324+
@property
325+
def username(self):
326+
return self.__username
327+
328+
329+
class PasswordCredentials(Credentials):
330+
"""Plugin class for CredentialsResult with password to be used for plugin operations
331+
and library functions.
332+
333+
Plugin authors should use this instead of the corresponding protobuf generated
334+
class.
335+
336+
Args:
337+
username: User name.
338+
password: Password.
339+
"""
340+
def __init__(self, username, password):
341+
super(PasswordCredentials, self).__init__(username)
342+
if not isinstance(password, basestring):
343+
raise IncorrectTypeError(
344+
PasswordCredentials,
345+
'password',
346+
type(password),
347+
basestring)
348+
self.__password = password
349+
350+
@property
351+
def password(self):
352+
return self.__password
353+
354+
@staticmethod
355+
def from_proto(credentials_result):
356+
"""Converts protobuf class libs_pb2.CredentialsResult to plugin class PasswordCredentials
357+
"""
358+
if not isinstance(credentials_result, libs_pb2.CredentialsResult):
359+
raise IncorrectTypeError(
360+
PasswordCredentials,
361+
'credentials_result',
362+
type(credentials_result),
363+
libs_pb2.CredentialsResult)
364+
return PasswordCredentials(
365+
username=credentials_result.username, password=credentials_result.pasword)
366+
367+
368+
class KeyPairCredentials(Credentials):
369+
"""Plugin class for CredentialsResult with key pair to be used for plugin operations
370+
and library functions.
371+
372+
Plugin authors should use this instead of the corresponding protobuf generated
373+
class.
374+
375+
Args:
376+
username (str): User name.
377+
private_key (str): Private key.
378+
public_key (str): Public key corresponding to private key. Empty string if not present.
379+
"""
380+
def __init__(self, username, private_key, public_key):
381+
super(KeyPairCredentials, self).__init__(username)
382+
if not isinstance(private_key, basestring):
383+
raise IncorrectTypeError(
384+
KeyPairCredentials,
385+
'private_key',
386+
type(private_key),
387+
basestring)
388+
self.__private_key = private_key
389+
if not isinstance(public_key, basestring):
390+
raise IncorrectTypeError(
391+
KeyPairCredentials,
392+
'public_key',
393+
type(public_key),
394+
basestring)
395+
self.__public_key = public_key
396+
397+
@property
398+
def private_key(self):
399+
return self.__private_key
400+
401+
@property
402+
def public_key(self):
403+
return self.__public_key
404+
405+
@staticmethod
406+
def from_proto(credentials_result):
407+
"""Converts protobuf class libs_pb2.CredentialsResult to plugin class KeyPairCredentials
408+
"""
409+
if not isinstance(credentials_result, libs_pb2.CredentialsResult):
410+
raise IncorrectTypeError(
411+
KeyPairCredentials,
412+
'credentials_result',
413+
type(credentials_result),
414+
libs_pb2.CredentialsResult)
415+
return KeyPairCredentials(
416+
username=credentials_result.username,
417+
private_key=credentials_result.key_pair.private_key,
418+
public_key=credentials_result.key_pair.public_key)

docs/docs/Best_Practices/Sensitive_Data.md

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Plugins must be careful to handle sensitive data appropriately. Three tips for h
1010

1111
Each of these tips are explained below.
1212

13-
# Marking Your Data As Sensitive
13+
## Marking Your Data As Sensitive
1414

1515
Because the Delphix Engine manages the storing and retrieving of plugin-defined data, it needs to know which pieces of data are sensitive. The plugin does this in its [schemas](/References/Glossary.md#schema), by using the special [`password`](/References/Schemas.md#password) keyword.
1616

@@ -37,11 +37,45 @@ This tells the Delphix Engine to take special precautions with this password pro
3737
!!! note
3838
Removing a previously added password property from a field and running a [Data Migration](/References/Glossary.md#data-migration) will expose the password in plaintext. If this is intentional, write a migration to ensure that the new property conforms to the new schema.
3939

40-
# Using Environment Variables For Remote Data Passing
40+
## Protecting Sensitive Data with Password Vaults
41+
42+
Plugins can also leverage the password vaults configured in the Delphix engine to avoid storing sensitive data in the engine itself. In addition, vaults can rotate secrets seamlessly behind the scenes without requiring Delphix users to update those secrets in the engine. To give users the option to choose between directly entering a secret, such as a password or private key, or retrieving it from a vault, Delphix provides [pre-defined credential types](/References/Schemas.md#delphix-specific-pre-defined-types).
43+
44+
When using these special types, the example above becomes:
45+
46+
```json
47+
{
48+
"type": "object",
49+
"properties": {
50+
"db_connectionPort": {"type": "string"},
51+
"db_credentials_supplier": {
52+
"$ref": "https://delphix.com/platform/api#/definitions/passwordCredentialsSupplier"
53+
}
54+
}
55+
}
56+
```
57+
58+
For details on how the user can provide the information required for a property such as `db_credentials_supplier`, see the [section on pre-defined types](/References/Schemas.md#delphix-specific-pre-defined-types).
59+
60+
At runtime, the plugin code must convert the credentials information provided by the user into an actual set of credentials that the plugin can use. To do this, the plugin must call the library function [`retrieve_credentials`](/References/Platform_Libraries.md#retrieve_credentials). For example:
61+
62+
```python
63+
from dlpx.virtualization import libs
64+
from dlpx.virtualization.common import PasswordCredentials
65+
...
66+
@plugin.virtual.stop()
67+
def my_virtual_stop(virtual_source, repository, source_config):
68+
credentials = libs.retrieve_credentials(virtual_source.parameters.db_credentials_supplier)
69+
assert isinstance(credentials, PasswordCredentials)
70+
connect_to_dbms(credentials.username, credentials.password)
71+
```
72+
73+
74+
## Using Environment Variables For Remote Data Passing
4175

4276
Sometimes, a plugin will need to pass sensitive data to a remote environment. For example, perhaps a database command needs to be run on a [staging environment](/References/Glossary.md#staging-environment), and that database command will need to use a password.
4377

44-
## Example
78+
### Example
4579
Let us take a look at a very simple example where we need to shutdown a database called "inventory" on a target environment by using the `db_cmd shutdown inventory` command. This command will ask for a password on `stdin`, and for our example our password is "hunter2".
4680

4781
If we were running this command by hand, it might look like this:
@@ -59,7 +93,7 @@ Since a plugin cannot type in the password by hand, it will do something like th
5993
$ echo "hunter2" | db_cmd shutdown inventory
6094
```
6195

62-
## Don't Do This
96+
### Don't Do This
6397

6498
First, let us take a look at how **not** to do this! Here is a bit of plugin python code that will run the above command.
6599

@@ -80,7 +114,7 @@ This constructs a Python string containing exactly the desired command from abov
80114

81115
The problem here is that there is a cleartext password in the Python string. But, this Python string is not treated as sensitive by the Virtualization Platform. For example, suppose the Virtualization Platform cannot make a connection to the target environment. In which case, it will raise an error containing the Python string, so that people will know what command failed. But, in our example, that would result in the password being part of the cleartext error message.
82116

83-
## Using Environment Variables
117+
### Using Environment Variables
84118

85119
The Delphix Engine provides a better way to pass sensitive data to remote bash (or powershell) calls: environment variables. Let us look at a different way to run the same command as above.
86120

@@ -106,7 +140,7 @@ Once the command runs on the target environment, Bash will substitute in the pas
106140

107141
Unlike with the command string, the Virtualization Platform **does** treat environment variables as sensitive information, and will not include them in error messages or internal logs, etc.
108142

109-
# Don't Write Out Sensitive Data
143+
## Don't Write Out Sensitive Data
110144

111145
Plugin writers are strongly advised to never write out unencrypted sensitive data. This is common-sense general advice that applies to all areas of programming, not just for plugins. However, there are a couple of special concerns for plugins.
112146

docs/docs/Building_Your_First_Plugin/Data_Ingestion.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ quite limiting.
2121

2222
For our first plugin, we will be using the more flexible [staging](/References/Glossary.md#staged-linkingsyncing) strategy. With this strategy, the Delphix Engine uses NFS for Unix environments (or iSCSI on Windows environments) to mount storage onto a [staging environment](/References/Glossary.md#staging-environment). Our plugin will then be in full control of how to get data from the source environment onto this storage mount.
2323

24-
With the staging strategy, there are two types of syncs: sync and resync. A `sync` is used to ingest incremental changes while a `resync` is used to re-ingest all the data for the dSource. For databases, this could mean re-ingesting from a full database backup to reset the dSource. A `sync` and a `resync` will execute the same plugin operations. To differentiate a `sync` from a `resync`, simply add a boolean property (i.e. `resync`) in the plugin's [snapshot parameters definition](References/Schemas_and_Autogenerated_Classes.md#snapshotparametersdefinition-schema). Once `sync` or `resync` is selected, the property will be passed into [linked.pre_snapshot](/References/Plugin_Operations.md#staged-linked-source-pre-snapshot) and [linked.post_snapshot](/References/Plugin_Operations.md#staged-linked-source-post-snapshot) as a [snapshot parameter](/References/Glossary.md#snapshot-parameters).
24+
With the staging strategy, there are two types of syncs: sync and resync. A `sync` is used to ingest incremental changes while a `resync` is used to re-ingest all the data for the dSource. For databases, this could mean re-ingesting from a full database backup to reset the dSource. A `sync` and a `resync` will execute the same plugin operations. To differentiate a `sync` from a `resync`, simply add a boolean property (i.e. `resync`) in the plugin's [snapshot parameters definition](/References/Schemas_and_Autogenerated_Classes.md#snapshotparametersdefinition-schema). Once `sync` or `resync` is selected, the property will be passed into [linked.pre_snapshot](/References/Plugin_Operations.md#staged-linked-source-pre-snapshot) and [linked.post_snapshot](/References/Plugin_Operations.md#staged-linked-source-post-snapshot) as a [snapshot parameter](/References/Glossary.md#snapshot-parameters).
2525

2626
A regular `sync` is the default and is executed as part of policy driven syncs. A `resync` is only executed during initial ingestion or if the Delphix user manually starts one. The customer can manually trigger a `resync` via the UI by selecting the dSource, going to more options and selecting **Resynchronize dSource**. ![Screenshot](images/Resync.png)
2727

0 commit comments

Comments
 (0)