-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #50 from cloudblue/feature/playground
Inital commit of playground feature
- Loading branch information
Showing
15 changed files
with
489 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
# This file is part of the Ingram Micro Cloud Blue Connect connect-cli. | ||
# Copyright (c) 2021 Ingram Micro. All Rights Reserved. | ||
import os | ||
import sys | ||
|
||
import click | ||
|
||
from connect.cli.core.config import pass_config | ||
from connect.cli.plugins.play.context import Context | ||
|
||
|
||
@click.group(name='play', short_help='Play connect scripts.') | ||
def grp_play(): | ||
pass | ||
|
||
|
||
class PlayOptions: | ||
context_file = 'context.json' | ||
|
||
|
||
class PassArgumentDecorator: | ||
def __init__(self, arg): | ||
self.obj = arg | ||
|
||
def __call__(self, f): | ||
def wrapped(*args, **kwargs): | ||
f(self.obj, *args, **kwargs) | ||
|
||
return wrapped | ||
|
||
|
||
pass_arg = PassArgumentDecorator | ||
|
||
|
||
def setup_script_command(cls): | ||
@pass_config | ||
@pass_arg(cls) | ||
def cmd_play_custom(script_class, config, **kwargs): | ||
config.validate() | ||
|
||
Context.context_file_name = PlayOptions.context_file | ||
ctx = Context.create(**kwargs) | ||
|
||
if 'endpoint' not in ctx or not ctx.endpoint: | ||
ctx.endpoint = config.active.endpoint | ||
|
||
if 'distributor_account_token' not in ctx or not ctx.distributor_account_token: | ||
ctx.distributor_account_token = config.active.api_key | ||
|
||
ctx | script_class() | ||
ctx.save() | ||
|
||
options = cls.options() | ||
for o in options: | ||
cmd_play_custom = click.option(*o.args, **o.kwargs)(cmd_play_custom) | ||
|
||
grp_play.command(name=cls.command(), short_help=cls.help())(cmd_play_custom) | ||
|
||
|
||
def load_one_script(scripts, filename): | ||
modname = filename[0:-3] | ||
|
||
try: | ||
mod = __import__(modname, globals={"__name__": __name__}, fromlist=['*']) | ||
|
||
if not hasattr(mod, '__all__'): | ||
print(f'Warning: {filename} has no __all__ defined', file=sys.stderr) | ||
return | ||
|
||
for cname in mod.__all__: | ||
cls = getattr(mod, cname) | ||
setup_script_command(cls) | ||
|
||
except Exception as e: | ||
print(f'Failed to import {scripts}/{filename}: {e}') | ||
|
||
|
||
def load_scripts_actions(): | ||
scripts = os.environ.get('CCLI_SCRIPTS', 'scripts') | ||
if scripts[0] != '/': | ||
scripts = os.path.join(os.getcwd(), scripts) | ||
|
||
if os.path.isdir(scripts): | ||
print(f'Reading scripts library from {scripts}') | ||
sys.path.append(scripts) | ||
|
||
for filename in sorted(os.listdir(scripts)): | ||
if not filename.endswith('.py') or filename[0] == '_': | ||
continue | ||
|
||
load_one_script(scripts, filename) | ||
|
||
|
||
load_scripts_actions() | ||
|
||
|
||
def get_group(): | ||
return grp_play |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import inspect | ||
import json | ||
import sys | ||
|
||
|
||
class Context(dict): | ||
context_file_name = None | ||
|
||
@classmethod | ||
def create_from_file(cls, filename=context_file_name): | ||
ctx = cls() | ||
try: | ||
ctx.load(filename) | ||
except FileNotFoundError: | ||
pass | ||
|
||
return ctx | ||
|
||
@classmethod | ||
def create(cls, args=None, filename=context_file_name, **kwargs): | ||
ctx = cls() | ||
try: | ||
ctx.load(filename) | ||
except FileNotFoundError: | ||
pass | ||
|
||
if args: | ||
ctx.parse_args(args) | ||
|
||
if kwargs: | ||
for k, v in kwargs.items(): | ||
if v is not None: | ||
ctx[k] = v | ||
|
||
return ctx | ||
|
||
def parse_args(self, args): | ||
for k, v in [a.split('=') for a in args]: | ||
self[k] = v | ||
|
||
def load(self, filename=context_file_name): | ||
if filename: | ||
with open(filename) as f: | ||
print(f'Loading context from {filename}', file=sys.stderr) | ||
self.clear() | ||
for k, v in json.load(f).items(): | ||
self[k] = v | ||
|
||
def save(self, filename=context_file_name): | ||
if filename: | ||
with open(filename, 'w') as f: | ||
print(f'Saving context into {filename}', file=sys.stderr) | ||
json.dump(self, f, indent=4) | ||
|
||
def __str__(self): | ||
return json.dumps(self, indent=4) | ||
|
||
def __getattr__(self, item): | ||
if item in self: | ||
return self[item] | ||
|
||
raise KeyError(item) | ||
|
||
def __setattr__(self, key, value): | ||
self[key] = value | ||
|
||
def __ior__(self, kv): | ||
key, value = kv | ||
if isinstance(value, dict): | ||
if key not in self: | ||
self[key] = {} | ||
self[key].update(value) | ||
else: | ||
if not isinstance(value, list): | ||
value = [value] | ||
|
||
if key not in self: | ||
self[key] = [] | ||
|
||
self[key].extend(value) | ||
|
||
return self | ||
|
||
def __or__(self, step): | ||
if inspect.isclass(step): | ||
step = step() | ||
|
||
step.do(context=self) | ||
return self |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
from connect.cli.plugins.play.context import Context | ||
from connect.cli.plugins.play.script import Script | ||
|
||
|
||
class Save(Script): | ||
def do(self, filename=Context.context_file_name, context=None): | ||
super().do(context=context) | ||
self.context.save(filename=filename) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import re | ||
from typing import List | ||
|
||
from connect.cli.plugins.play.context import Context | ||
from connect.client import ConnectClient | ||
|
||
|
||
class OptionWrapper: | ||
def __init__(self, *args, **kwargs): | ||
self.args = args | ||
self.kwargs = kwargs | ||
|
||
|
||
class Script: | ||
context: Context = None | ||
endpoint: str = None | ||
|
||
def __init__(self, context=None, **kwargs): | ||
self.context = context if context is not None else Context() | ||
self.context.update(kwargs) | ||
|
||
@classmethod | ||
def command(cls) -> str: | ||
return str(re.sub(r'^([A-Z])', lambda x: x.group(1).lower(), | ||
re.sub(r'([a-z])([A-Z])', lambda x: f'{x.group(1)}-{x.group(2).lower()}', cls.__name__))) | ||
|
||
@classmethod | ||
def help(cls) -> str: | ||
return cls.__doc__ | ||
|
||
@classmethod | ||
def options(cls) -> List[OptionWrapper]: | ||
return [] | ||
|
||
def client(self, token) -> ConnectClient: | ||
return ConnectClient(token, endpoint=self.context.endpoint, use_specs=False) | ||
|
||
@property | ||
def dclient(self) -> ConnectClient: | ||
return self.client(self.context.distributor_account_token) | ||
|
||
@property | ||
def vclient(self) -> ConnectClient: | ||
return self.client(self.context.vendor_account_token) | ||
|
||
def do(self, context=None): | ||
if context: | ||
context.update(self.context) | ||
self.context = context |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"endpoint": "https://api.cnct.info/public/v1", | ||
"program_agreement_id": "AGP-927-440-678", | ||
"distribution_agreements": [ | ||
"AGD-199-236-391", | ||
"AGD-669-983-907" | ||
], | ||
"program_contract_id": "CRP-41033-36725-76650", | ||
"vendor_account_id": "VA-677-276" | ||
} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
|
||
import random | ||
import sys | ||
|
||
from connect.cli.plugins.play.context import Context | ||
from connect.cli.plugins.play.script import OptionWrapper, Script | ||
from connect.cli.plugins.play.save import Save | ||
|
||
|
||
class Script1(Script): | ||
"""CLI help for Script1.""" | ||
|
||
@classmethod | ||
def options(cls): | ||
return [ | ||
OptionWrapper('--some_id', help='Script1 IDs'), | ||
OptionWrapper('--account_token', help='Script1 account token'), | ||
] | ||
|
||
def do(self, context=None): | ||
super().do(context=context) | ||
print('--- Init Script 1 ---') | ||
|
||
|
||
__all__ = ('Script1',) | ||
|
||
if __name__ == '__main__': | ||
try: | ||
ctx = Context.create(sys.argv[1:]) | ||
ctx | Script1 | Save | ||
print(ctx) | ||
except Exception as e: | ||
print(e) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
|
||
import random | ||
import sys | ||
|
||
from connect.cli.plugins.play.context import Context | ||
from connect.cli.plugins.play.script import OptionWrapper, Script | ||
from connect.cli.plugins.play.save import Save | ||
|
||
|
||
class Script2(Script): | ||
"""CLI help for Script2.""" | ||
|
||
@classmethod | ||
def options(cls): | ||
return [ | ||
OptionWrapper('--account_token', help='Script2 account token'), | ||
] | ||
|
||
def do(self, context=None): | ||
super().do(context=context) | ||
print('--- Init Script 2 ---') | ||
|
||
|
||
# __all__ = ('Script2',) # intentional mistake | ||
|
||
if __name__ == '__main__': | ||
try: | ||
ctx = Context.create(sys.argv[1:]) | ||
ctx | Script2 | Save | ||
print(ctx) | ||
except Exception as e: | ||
print(e) |
Oops, something went wrong.