Skip to content

Commit

Permalink
Version 2.1.8
Browse files Browse the repository at this point in the history
  • Loading branch information
mminichino committed Dec 19, 2023
1 parent aee8db6 commit fdd0ffd
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 7 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 2.1.7
current_version = 2.1.8
commit = False
tag = False
message = 'Version {new_version}'
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# cb-util 2.1.7
# cb-util 2.1.8

## Couchbase Utilities
Couchbase connection manager. Simplifies connecting to a Couchbase cluster and performing data and management operations.
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.1.7
2.1.8
2 changes: 1 addition & 1 deletion cbcmgr/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from pkg_resources import parse_version

_ROOT = os.path.abspath(os.path.dirname(__file__))
__version__ = "2.1.7"
__version__ = "2.1.8"
VERSION = parse_version(__version__)


Expand Down
68 changes: 68 additions & 0 deletions cbcmgr/cb_capella.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,36 @@ def from_cbs(cls, username: str, password: str, roles: List[Role]):
)


@attr.s
class UserOpValue:
id: Optional[str] = attr.ib(default=None)
type: Optional[str] = attr.ib(default=None)
roles: Optional[List[str]] = attr.ib(default=None)


@attr.s
class UserOp:
op: Optional[str] = attr.ib(default=None)
path: Optional[str] = attr.ib(default=None)
value: Optional[UserOpValue] = attr.ib(default=None)


@attr.s
class UserOpList:
user_op_list: Optional[List[UserOp]] = attr.ib(default=[])

def add(self, project_id: str, role: str):
opo = UserOp()
opo.op = "add"
opo.path = f"/resources/{project_id}"
opo.value = UserOpValue(id=project_id, type="project", roles=[role])
self.user_op_list.append(opo)

@property
def as_dict(self):
return list(attrs.asdict(o) for o in self.__dict__["user_op_list"])


class NetworkDriver(object):

def __init__(self):
Expand Down Expand Up @@ -395,6 +425,16 @@ def get_organization(self, name: str):

return next((o for o in results.get('data', []) if o.get('name') == name), None)

def list_users(self):
results = self.api_get(f"/v4/organizations/{self.organization_id}/users").json()

return results

def get_user(self, name: str = None, email: str = None):
results = self.api_get(f"/v4/organizations/{self.organization_id}/users").json()

return next((u for u in results if u.get('name') == name or u.get('email') == email), None)

def list_projects(self):
results = self.api_get(f"/v4/organizations/{self.organization_id}/projects").json()

Expand All @@ -405,6 +445,34 @@ def get_project(self, name: str):

return next((p for p in results if p.get('name') == name), None)

def create_project(self, name: str, username: str = None, email: str = None):
parameters = {"name": name}
try:
results = self.api_post(f"/v4/organizations/{self.organization_id}/projects", body=parameters).json()
project_id = results.get('id')
if username is not None or email is not None:
self.set_project_owner(project_id, username, email)
return project_id
except APIError as err:
raise CapellaError(f"Can not create project: {err} message: {err.body.get('message', '')}")

def set_project_owner(self, project_id: str, name: str = None, email: str = None):
if name is None and email is None:
raise ValueError("Either name or email must be provided")
user = self.get_user(name, email)
if not user:
raise CapellaError("User does not exist")

user_id = user.get('id')
user_op = UserOpList()
user_op.add(project_id, "projectOwner")
parameters = user_op.as_dict

try:
self.api_patch(f"/v4/organizations/{self.organization_id}/users/{user_id}", body=parameters).json()
except APIError as err:
raise CapellaError(f"Can not set project ownership: {err} message: {err.body.get('message', '')}")

def list_clusters(self):
results = self.api_get(f"/v4/organizations/{self.organization_id}/projects/{self.project_id}/clusters").json()

Expand Down
34 changes: 31 additions & 3 deletions cbcmgr/cli/caputil.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
##
##

import json
import argparse
import warnings
from overrides import override
Expand Down Expand Up @@ -35,6 +36,7 @@ def local_args(self):
opt_parser.add_argument('-c', '--cidr', action='store', help="Cluster CIDR", default="10.0.0.0/23")
opt_parser.add_argument('-m', '--machine', action='store', help="Machine type", default="4x16")
opt_parser.add_argument('-U', '--user', action='store', help="User Name", default="Administrator")
opt_parser.add_argument('-e', '--email', action='store', help="User Email")
opt_parser.add_argument('-P', '--password', action='store', help="User Password")
opt_parser.add_argument('-C', '--cloud', action='store', help="Cluster cloud", default="aws")
opt_parser.add_argument('-R', '--region', action='store', help="Cloud region", default="us-east-1")
Expand All @@ -57,6 +59,7 @@ def local_args(self):
project_subparser = project_parser.add_subparsers(dest='project_command')
project_subparser.add_parser('get', help="Get project info", parents=[opt_parser], add_help=False)
project_subparser.add_parser('list', help="List projects", parents=[opt_parser], add_help=False)
project_subparser.add_parser('owner', help="Set project owner", parents=[opt_parser], add_help=False)
org_parser = command_subparser.add_parser('org', help="Cluster Operations", parents=[opt_parser], add_help=False)
org_subparser = org_parser.add_subparsers(dest='org_command')
org_subparser.add_parser('get', help="Get organization info", parents=[opt_parser], add_help=False)
Expand All @@ -66,9 +69,13 @@ def local_args(self):
bucket_subparser.add_parser('create', help="Create bucket", parents=[opt_parser], add_help=False)
bucket_subparser.add_parser('delete', help="Delete bucket", parents=[opt_parser], add_help=False)
bucket_subparser.add_parser('list', help="List buckets", parents=[opt_parser], add_help=False)
credential_parser = command_subparser.add_parser('credential', help="DB Credential Operations", parents=[opt_parser], add_help=False)
credential_subparser = credential_parser.add_subparsers(dest='credential_command')
credential_subparser.add_parser('password', help="Change password", parents=[opt_parser], add_help=False)
user_parser = command_subparser.add_parser('user', help="User Operations", parents=[opt_parser], add_help=False)
user_subparser = user_parser.add_subparsers(dest='user_command')
user_subparser.add_parser('password', help="Change password", parents=[opt_parser], add_help=False)
user_subparser.add_parser('get', help="Get user", parents=[opt_parser], add_help=False)
user_subparser.add_parser('list', help="List users", parents=[opt_parser], add_help=False)

def create_cluster(self, project_id: str):
cluster_name = self.options.name
Expand Down Expand Up @@ -195,6 +202,13 @@ def change_password(self, project_id: str):
else:
logger.error("Password does not meet complexity requirements")

def set_project_owner(self, project_id: str):
username = self.options.name
email = self.options.email

logger.info(f"Setting ownership of project")
Capella().set_project_owner(project_id, username, email)

def run(self):
logger.info("CapUtil version %s" % VERSION)
cm = Capella()
Expand Down Expand Up @@ -263,6 +277,10 @@ def run(self):
elif self.options.bucket_command == "list":
print(pd.DataFrame(subset_df).to_string())
elif self.options.command == 'project':
if self.options.project_command == "owner":
self.set_project_owner(project_id)
return

data = cm.list_projects()
df = pd.json_normalize(data)
subset_df = df[["id", "name", "audit.createdAt", "description"]]
Expand All @@ -284,13 +302,23 @@ def run(self):
print(result)
elif self.options.org_command == "list":
print(pd.DataFrame(subset_df).to_string())
elif self.options.command == 'user':
elif self.options.command == 'credential':
if not project_id:
logger.error(f"Can not find project {self.options.project}")
return

if self.options.user_command == "password":
if self.options.credential_command == "password":
self.change_password(project_id)
elif self.options.command == 'user':
if self.options.user_command == "get":
result = cm.get_user(self.options.name, self.options.email)
if result:
print(json.dumps(result, indent=2))
elif self.options.user_command == "list":
data = cm.list_users()
df = pd.json_normalize(data)
subset_df = df[["id", "name", "email"]]
print(pd.DataFrame(subset_df).to_string())


def main(args=None):
Expand Down
15 changes: 15 additions & 0 deletions cbcmgr/httpsessionmgr.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,3 +324,18 @@ def api_delete(self, endpoint):

self._response = response.text
return self

def api_patch(self, endpoint, body):
response = self.session.patch(self.url_prefix + endpoint,
auth=self.auth_class,
json=body,
verify=False,
timeout=self.timeout)

try:
self.check_status_code(response.status_code)
except Exception as err:
raise APIError(err, response.text, response.status_code) from err

self._response = response.text
return self

0 comments on commit fdd0ffd

Please sign in to comment.