Skip to content

Commit 2e99064

Browse files
author
Jordan Mance
committed
First commit yolo deploy?
1 parent f79a412 commit 2e99064

File tree

12 files changed

+393
-0
lines changed

12 files changed

+393
-0
lines changed

.github/workflows/deploy-pypi.yml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: Build & Test
2+
3+
on:
4+
push:
5+
branches: [master]
6+
pull_request:
7+
branches: [master]
8+
9+
jobs:
10+
release:
11+
if: github.ref == 'refs/heads/master'
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v2
15+
- name: Set up Python 3.8
16+
uses: actions/setup-python@v2
17+
with:
18+
python-version: 3.8
19+
20+
- name: Install dependencies
21+
run: |
22+
sudo apt-get install libusb-1.0-0-dev
23+
python -m pip install --upgrade pip
24+
pip install setuptools twine pip wheel
25+
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
26+
27+
- name: Upload To PyPi
28+
env:
29+
PYPI_RC: ${{ secrets.PYPI_FIGGY_CLI }}
30+
run: |
31+
echo "${PYPI_RC}" > ~/.pypirc
32+
cd src
33+
./scripts/deploy-pypi.sh
34+
35+
- name: Prep Release
36+
id: prep
37+
run: |
38+
cd cli
39+
# Parse version
40+
VERSION=$(./scripts/get_version.sh)
41+
echo "Setting release version to $VERSION"
42+
echo "::set-env name=RELEASE_VERSION::$VERSION"
43+
44+
# Parse changelog
45+
CHANGELOG=$(cat CHANGELOG.md)
46+
47+
echo "Got changelog: $CHANGELOG"
48+
echo "::set-output name=changelog::$CHANGELOG"
49+
50+
- name: Create Release
51+
id: create_release
52+
uses: actions/create-release@master
53+
env:
54+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
55+
with:
56+
tag_name: ${{ env.RELEASE_VERSION }}
57+
release_name: Release ${{ env.RELEASE_VERSION }}
58+
body: ${{ steps.prep.outputs.changelog }}
59+
draft: false
60+
prerelease: true

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Python Figgy Lib Changelog
2+
3+
## 0.0.1
4+
- Initial alpha release + testing

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
# figgy.python.lib
22
Contains a public python library that may be used be Figgy users to simply application config management.
3+
4+
Start using figgy today: https://www.figgy.dev/
5+
6+
Check out the detailed docs: https://www.figgy.dev/docs/

src/figgy/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .fig_store import FigStore
2+
from .fig_svc import FigService
3+
from .writer import ConfigWriter
4+
from .figs import AppFig, ReplicatedFig, SharedFig, MergeFig

src/figgy/fig_store.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import logging
2+
from dataclasses import dataclass
3+
from typing import List
4+
5+
from .fig_svc import FigService
6+
7+
log = logging.getLogger(__name__)
8+
9+
ENV_LOCAL_RUN = 'LOCAL_RUN'
10+
11+
12+
# All PS configurations are defined in our FigStore
13+
@dataclass
14+
class FigStore:
15+
TWIG: str
16+
17+
@property
18+
def figs(self) -> List[str]:
19+
return [a for a in dir(self) if not a.startswith('__') and not a == 'TWIG' and not a == 'figs']
20+
21+
def __init__(self, fig_svc: FigService, lazy_load: bool = False):
22+
# A little magic, setting the twig attribute for each FIG for a better user experience
23+
for fig in self.figs:
24+
self.__getattribute__(fig).twig = self.TWIG
25+
26+
if lazy_load:
27+
for fig in self.figs:
28+
self.__getattribute__(fig).fig_svc = fig_svc
29+
else:
30+
for fig in self.figs:
31+
self.__getattribute__(fig).value = fig_svc.get_fig(self.__getattribute__(fig).name, prefix=self.TWIG)

src/figgy/fig_svc.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from typing import Optional
2+
3+
from botocore.exceptions import ClientError
4+
5+
from .utils import Utils
6+
7+
8+
class FigService:
9+
10+
def __init__(self, ssm):
11+
self._ssm = ssm
12+
13+
@Utils.retry
14+
def get_fig(self, name: str, prefix: Optional[str] = None) -> Optional[str]:
15+
"""
16+
Gets a parameter, returns None if parameter doesn't exist.
17+
Args:
18+
name: The PS Name - E.G. /app/demo-time/parameter/abc123
19+
prefix: Namespace prefix for this parameter. I.E. '/app/demo-time
20+
Returns: str -> Parameter's value
21+
"""
22+
23+
if prefix:
24+
if not name.startswith(prefix):
25+
name = f'{prefix.rstrip("/")}/{name.lstrip("/")}'
26+
try:
27+
parameter = self._ssm.get_parameter(Name=name, WithDecryption=True)
28+
return parameter['Parameter']['Value']
29+
except ClientError as e:
30+
if "ParameterNotFound" == e.response['Error']['Code']:
31+
return None
32+
else:
33+
raise

src/figgy/figs.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import logging
2+
import os
3+
from abc import ABC
4+
from typing import Optional, List, Union
5+
6+
# JSON Key Constants
7+
from .fig_svc import FigService
8+
9+
FIG_MISSING = "FIG NOT SET"
10+
log = logging.getLogger(__name__)
11+
12+
13+
class ConfigurationMissingException(Exception):
14+
def __init__(self, fig_name):
15+
super().__init__(f"The configuration: {fig_name} "
16+
f"was not found in Parameter Store or as an environment variable override.")
17+
18+
19+
class Fig(ABC):
20+
twig: str = None
21+
22+
"""
23+
Represents a single fig in ParameterStore.
24+
"""
25+
26+
def __init__(self, name: str, twig: Optional[str] = None):
27+
self._name = name
28+
self.twig = twig
29+
30+
@property
31+
def env_name(self):
32+
return self.base_name.replace("/", "_").replace("-", "_").upper().rstrip("_").lstrip("_")
33+
34+
35+
@property
36+
def base_name(self):
37+
return self._name
38+
39+
@property
40+
def name(self):
41+
if self.twig:
42+
if not self._name.startswith(self.twig):
43+
return f'{self.twig.rstrip("/")}/{self._name.lstrip("/")}'
44+
else:
45+
return self._name
46+
47+
@name.setter
48+
def name(self, name: str):
49+
self._name = name
50+
51+
@property
52+
def fig_svc(self):
53+
return self._fig_svc
54+
55+
@fig_svc.setter
56+
def fig_svc(self, fig_svc: FigService):
57+
self._fig_svc = fig_svc
58+
59+
@property
60+
def value(self) -> str:
61+
if os.environ.get(self.env_name):
62+
self._value = os.environ.get(self.env_name)
63+
log.debug(f'{self.env_name} found in environment. Using value: {self._value}')
64+
else:
65+
if not hasattr(self, '_value') or not self._value:
66+
if hasattr(self, '_fig_svc') and self._fig_svc:
67+
print(f"LOOKING UP FIG: {self.name}")
68+
self._value = self._fig_svc.get_fig(self.name)
69+
70+
# Either we can't find the value, or there is a bug in this software
71+
if not self._value:
72+
raise ConfigurationMissingException(self.name)
73+
74+
return self._value
75+
76+
@value.setter
77+
def value(self, value):
78+
self._value = value
79+
80+
def __str__(self):
81+
return self.value if self.value else FIG_MISSING
82+
83+
84+
class AppFig(Fig):
85+
default: Optional[str] = None
86+
87+
def __init__(self, name: str, default: Optional[str] = None):
88+
super().__init__(name=name)
89+
self.default = default
90+
91+
92+
class ReplicatedFig(Fig):
93+
source: str
94+
95+
def __init__(self, name: str, source: str):
96+
super().__init__(name=name)
97+
self.source = source
98+
99+
100+
class SharedFig(Fig):
101+
def __init__(self, name: str):
102+
super().__init__(name=name)
103+
104+
105+
class MergeFig(Fig):
106+
pattern: List[Union[str, Fig]]
107+
108+
def __init__(self, name: str, pattern: List[Union[str, Fig]]):
109+
super().__init__(name=name)
110+
self.pattern = pattern
111+
112+
@property
113+
def pattern(self):
114+
translated_pattern = []
115+
for p in self._pattern:
116+
if hasattr(p, 'name'):
117+
translated_pattern.append(f'${{{p.name}}}')
118+
else:
119+
translated_pattern.append(p)
120+
121+
return translated_pattern
122+
123+
@pattern.setter
124+
def pattern(self, pattern: List[Union[str, Fig]]):
125+
self._pattern = pattern

src/figgy/utils.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import logging
2+
import time
3+
import botocore
4+
import urllib3
5+
6+
log = logging.getLogger(__name__)
7+
8+
BACKOFF = .25
9+
MAX_RETRIES = 5
10+
11+
12+
class Utils:
13+
14+
@staticmethod
15+
def retry(function):
16+
"""
17+
Decorator that supports automatic retries if connectivity issues are detected with boto or urllib operations
18+
"""
19+
20+
def inner(self, *args, **kwargs):
21+
retries = 0
22+
while True:
23+
try:
24+
return function(self, *args, **kwargs)
25+
except (botocore.exceptions.EndpointConnectionError, urllib3.exceptions.NewConnectionError) as e:
26+
print(e)
27+
if retries > MAX_RETRIES:
28+
raise e
29+
30+
self._utils.notify("Network connectivity issues detected. Retrying with back off...")
31+
retries += 1
32+
time.sleep(retries * BACKOFF)
33+
34+
return inner

src/figgy/writer.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import json
2+
from collections import OrderedDict
3+
from .fig_store import FigStore
4+
from .figs import ReplicatedFig, AppFig, SharedFig, MergeFig
5+
6+
TWIG = 'twig'
7+
APP_FIGS = 'app_figs'
8+
REPLICATE_FIGS = 'replicate_figs'
9+
SHARED_FIGS = 'shared_figs'
10+
MERGED_FIGS = 'merged_figs'
11+
12+
13+
class ConfigWriter:
14+
"""
15+
Writes the figgy.json file into the provided directory. If no directory is provided, writes the figgy.json
16+
to the local directory.
17+
"""
18+
19+
@staticmethod
20+
def write(fig_store: FigStore, file_name: str = "figgy.json", destination_dir=""):
21+
destination_dir = destination_dir.rstrip("/")
22+
23+
figgy_config: OrderedDict = OrderedDict()
24+
figgy_config[TWIG] = fig_store.TWIG
25+
figgy_config[APP_FIGS] = []
26+
figgy_config[REPLICATE_FIGS] = {}
27+
figgy_config[SHARED_FIGS] = []
28+
figgy_config[MERGED_FIGS] = {}
29+
30+
for fig in fig_store.figs:
31+
item = fig_store.__getattribute__(fig)
32+
if isinstance(item, AppFig):
33+
figgy_config[APP_FIGS].append(item.name)
34+
elif isinstance(item, ReplicatedFig):
35+
figgy_config[REPLICATE_FIGS][item.source] = item.name
36+
elif isinstance(item, SharedFig):
37+
figgy_config[SHARED_FIGS].append(item.name)
38+
elif isinstance(item, MergeFig):
39+
figgy_config[MERGED_FIGS][item.name] = item.pattern
40+
41+
destination_dir = f'{destination_dir.rstrip("/")}/' if destination_dir else ''
42+
43+
print(figgy_config)
44+
45+
with open(f"{destination_dir}{file_name}", "w") as file:
46+
file.write(json.dumps(figgy_config, indent=4))

src/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
boto3==1.14.3

0 commit comments

Comments
 (0)