From 6d981ed36379e215d1601166dd29c462ad88c1d8 Mon Sep 17 00:00:00 2001
From: TechCarmat <techcarmat@pm.me>
Date: Tue, 16 Nov 2021 17:21:42 +0100
Subject: [PATCH 1/6] Improving auth.py with new utils functions

---
 cli/stackn/auth.py | 50 +++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 43 insertions(+), 7 deletions(-)

diff --git a/cli/stackn/auth.py b/cli/stackn/auth.py
index af6febf83..d695e7581 100644
--- a/cli/stackn/auth.py
+++ b/cli/stackn/auth.py
@@ -38,6 +38,22 @@
     'STACKN_SECURE': True
 }
 
+
+# STACKN_SECURE is by default always set to True. However we allow to deploy STACKn locally for development and testing purposes.
+# That's when a user needs to login with the flag --insecure, and thus STACKN_SECURE will be False
+# The util function make sure to remind the user to use --insecure if set in the ~/.scaleout/stackn.json configuration file
+def _check_flag_insecure(inp_conf):
+    stackn_conf_file = _load_config_file_full({})
+
+    # If the general default STACKN_SECURE is still True, but in the configuration file shows that 
+    # it is set to False instead (because the user has logged in with --insecure) Then we remind the user to use --insecure
+    if stackn_conf_file['current']['STACKN_SECURE'] == False and inp_conf['STACKN_SECURE']:
+        print("Do you perhaps have a local STACKn deployment? If so, don't forget to use --insecure to make stackn API calls working.")
+        return True
+    else:
+        return False
+
+
 def _get_stackn_config_path():
     if 'STACKN_CONFIG_PATH' in os.environ:
         config_path = os.environ['STACKN_CONFIG_PATH']
@@ -66,6 +82,19 @@ def _get_config_file(rw='r'):
     
     return fin
 
+
+# Checking if a user is logged. Promtping it if otherwise
+def _is_user_logged():
+    stackn_conf_file = _load_config_file_full({})
+
+    if not stackn_conf_file:
+        print("You are not logged in.")
+        print("Please use the command: 'stackn login -u <your-user> -p <your-user-password>' --url <your-studio-url>")
+        return False
+    else:
+        return True
+
+
 def _load_config_file_full(conf):
     stackn_config = []
     fin = _get_config_file()
@@ -178,6 +207,14 @@ def get_config(inp_config=dict(), required=[], is_login=False, print_warnings=Tr
     # Exception is STACKN_ACCESS_TOKEN, and STACKN_REFRESH_TOKEN, where config file
     # takes priority over environment variable (as they need to be updated)
 
+    # Checking if the user is logged in. Skipping if when the user needs to login instead
+    if not is_login and not _is_user_logged():
+        return False, False
+
+    # Checking if user has forgot to use --insecure flag
+    if _check_flag_insecure(inp_config):
+        return False, False
+
     conf = dict()
     # Fetch from environment variables
     for var, val in env_vars.items():
@@ -188,7 +225,7 @@ def get_config(inp_config=dict(), required=[], is_login=False, print_warnings=Tr
         else:
             conf[var] = val
 
-    # Fetch "current" remote, project, secure_mode
+    # Fetch "current" context: remote_url, project and secure_mode
     config_file_full = _load_config_file_full(conf)
     try:
         current = config_file_full['current']
@@ -200,9 +237,9 @@ def get_config(inp_config=dict(), required=[], is_login=False, print_warnings=Tr
     if not conf['STACKN_PROJECT']:
         if 'STACKN_PROJECT' in current:
             conf['STACKN_PROJECT'] = current['STACKN_PROJECT']
-    # if 'STACKN_SECURE' not in conf:
-    # print("AAA")
-    # print(conf)
+
+
+    # Checking flag STACKN_SECURE and initialize it if needed
     if 'STACKN_SECURE' in current and (conf['STACKN_SECURE']=="" or conf['STACKN_SECURE']==None):
         if print_warnings and current['STACKN_SECURE'] == False:
             print("Insecure mode is set in config, will not verify certificates.")
@@ -211,10 +248,9 @@ def get_config(inp_config=dict(), required=[], is_login=False, print_warnings=Tr
     # If we have a currently set remote URL, fetch from config file.
     if conf['STACKN_URL']:
         try:
-            # print(conf)
             conf['STACKN_KEYCLOAK_URL'] = get_keycloak_url(conf['STACKN_URL'], secure=conf['STACKN_SECURE'])
         except:
-            print("Failed to call studio endpoint at: {}".format(conf['STACKN_URL']))
+            print("Failed to call studio API endpoint at: {}".format(conf['STACKN_URL']))
             return conf, False
         config_file = _load_config_file_url(conf, is_login)
         if config_file:
@@ -266,7 +302,7 @@ def get_token(conf={}, write_to_file=True):
     
     conf, status = get_config(conf, required=['STACKN_REFRESH_TOKEN'])
     if not status:
-        print("Failed to get required config.")
+        print("Failed to get required STACKn configuration.")
         return conf, False
 
     token_url = urllib.parse.urljoin(conf["STACKN_KEYCLOAK_URL"], 'auth/realms/{}/protocol/openid-connect/token'.format(conf['STACKN_REALM']))

From 75ee6e58b059388073eeb63325cfbe72469343d1 Mon Sep 17 00:00:00 2001
From: TechCarmat <techcarmat@pm.me>
Date: Tue, 16 Nov 2021 17:28:11 +0100
Subject: [PATCH 2/6] Improving with: clean up and conditional basic checks.

---
 cli/stackn/stackn.py | 216 +++++++++++++++++++++++--------------------
 1 file changed, 117 insertions(+), 99 deletions(-)

diff --git a/cli/stackn/stackn.py b/cli/stackn/stackn.py
index 4c492037c..83c3195e2 100644
--- a/cli/stackn/stackn.py
+++ b/cli/stackn/stackn.py
@@ -3,7 +3,6 @@
 import uuid
 import requests
 import subprocess
-import urllib.parse
 
 import stackn.auth
 import stackn.s3
@@ -18,9 +17,11 @@ def _check_status(r, error_msg="Failed"):
         print(r.text)
         return False
     else:
-        return True 
+        return True
+
 
 def get_endpoints(studio_url):
+    # These endpoints are related to the API available in Studio under: stackn/components/studio/api/
     endpoints = dict()
     if (not 'http://' in studio_url) and (not 'https://' in studio_url):
         studio_url = 'https://'+studio_url
@@ -39,27 +40,37 @@ def get_endpoints(studio_url):
     endpoints['projects'] = base+'/projects/'
     endpoints['project_templates'] = base+'/projecttemplates/'
     endpoints['admin'] = dict()
-    # endpoints['admin']['apps'] = base+'/projects/{}/apps/'
     endpoints['admin']['apps'] = base+'/apps/'
     return endpoints
 
-def get_auth_headers(conf):
+def get_auth_header(conf):
     conf, status = stackn.auth.get_token(conf)
     if not status:
         return False, False
-    auth_headers = {"Authorization": "Token {}".format(conf['STACKN_ACCESS_TOKEN'])}
-    return auth_headers, conf
+    auth_header = {"Authorization": "Token {}".format(conf['STACKN_ACCESS_TOKEN'])}
+    return auth_header, conf
+
+def get_remote(inp_conf):
+    
+    conf, status = stackn.auth.get_config(inp_conf)
+
+    if not status:
+        return False
 
-def get_remote():
-    conf, status = stackn.auth.get_config()
     keys = stackn.auth._get_remote(conf)
+    
+    if not keys:
+        return False
+
     return keys
 
-def get_current(secure=True):
+def get_current(secure):
+
     res = {'STACKN_URL': False, 'STACKN_PROJECT': False}
     conf, status = stackn.auth.get_config({'STACKN_SECURE': secure})
+
     if not status:
-        print("Failed to get current STACKn and project.")
+        print("Failed to get current STACKn configuration file.")
     else:
         if 'STACKN_URL' in conf:
             res['STACKN_URL'] = conf['STACKN_URL']
@@ -68,19 +79,24 @@ def get_current(secure=True):
     
     return res
 
-def get_projects(conf={}, params=[], auth_headers=[]):
+def get_projects(conf={}, params=[], auth_header=[]):
     conf, status = stackn.auth.get_config(conf)
+
     if not status:
         print('Cannot list projects.')
         return False
-    if not auth_headers:
-        auth_headers, conf = get_auth_headers(conf)
+
+    auth_header, conf = get_auth_header(conf)
+
+    if not auth_header:
+        return False
+
     endpoints = get_endpoints(conf['STACKN_URL'])
     url = endpoints['projects']
     if params:
-        r = requests.get(url, headers=auth_headers, params=params, verify=conf['STACKN_SECURE'])
+        r = requests.get(url, headers=auth_header, params=params, verify=conf['STACKN_SECURE'])
     else:
-        r = requests.get(url, headers=auth_headers, verify=conf['STACKN_SECURE'])
+        r = requests.get(url, headers=auth_header, verify=conf['STACKN_SECURE'])
     if r:
         projects = json.loads(r.content)
         return projects
@@ -91,14 +107,25 @@ def get_projects(conf={}, params=[], auth_headers=[]):
         return None
 
 def call_admin_endpoint(name, conf={}, params=[]):
+    
     conf, status = stackn.auth.get_config(conf)
-    auth_headers, conf = get_auth_headers(conf)
+
+    if not status:
+        print("Failed to get current STACKn configuration file.")
+        return False
+
+    auth_header, conf = get_auth_header(conf)
+
+    if not auth_header:
+        return False
+    
     endpoints = get_endpoints(conf['STACKN_URL'])
     url = endpoints[name]
+
     if params:
-        r = requests.get(url, headers=auth_headers, params=params, verify=conf['STACKN_SECURE'])
+        r = requests.get(url, headers=auth_header, params=params, verify=conf['STACKN_SECURE'])
     else:
-        r = requests.get(url, headers=auth_headers, verify=conf['STACKN_SECURE'])
+        r = requests.get(url, headers=auth_header, verify=conf['STACKN_SECURE'])
     if r:
         objs = json.loads(r.content)
         return objs
@@ -110,12 +137,23 @@ def call_admin_endpoint(name, conf={}, params=[]):
 
 def call_project_endpoint(name, conf={}, params=[]):
     conf, status = stackn.auth.get_config(conf)
-    auth_headers, conf = get_auth_headers(conf)
+
+    if not status:
+        print("The configuration file for STACKn could not be correctly retrieved.")
+        return False
+
+    auth_header, conf = get_auth_header(conf)
+
+    if not auth_header:
+        return False
+
     endpoints = get_endpoints(conf['STACKN_URL'])
+    
     if conf['STACKN_PROJECT']:
         project = get_projects(conf, params={'name': conf['STACKN_PROJECT']})
     else:
         print("No project name specified.")
+        print("Try to run 'stackn get current' to check if a project is set.")
         return False
     if len(project)>1:
         print('Found several matching projects.')
@@ -126,9 +164,9 @@ def call_project_endpoint(name, conf={}, params=[]):
     project = project[0]
     url = endpoints[name].format(project['id'])
     if params:
-        r = requests.get(url, headers=auth_headers, params=params, verify=conf['STACKN_SECURE'])
+        r = requests.get(url, headers=auth_header, params=params, verify=conf['STACKN_SECURE'])
     else:
-        r = requests.get(url, headers=auth_headers, verify=conf['STACKN_SECURE'])
+        r = requests.get(url, headers=auth_header, verify=conf['STACKN_SECURE'])
     if r:
         projects = json.loads(r.content)
         return projects
@@ -145,12 +183,11 @@ def setup_project_endpoint_call(conf, endpoint_type):
         print("Missing required input (studio URL, project name).")
         return False
 
-    auth_headers, conf = get_auth_headers(conf)
-    if not auth_headers:
-        print("Failed to set authentication headers.")
+    auth_header, conf = get_auth_header(conf)
+    if not auth_header:
         return False
 
-    project = get_projects(conf=conf['STACKN_URL'], params={'name': conf['STACKN_PROJECT']}, auth_headers=auth_headers)
+    project = get_projects(conf=conf['STACKN_URL'], params={'name': conf['STACKN_PROJECT']}, auth_header=auth_header)
     if len(project)>1:
         print('Found several matching projects.')
         return
@@ -161,18 +198,23 @@ def setup_project_endpoint_call(conf, endpoint_type):
     project = project[0]
     endpoints = get_endpoints(conf['STACKN_URL'])
     url = endpoints[endpoint_type].format(project['id'])
-    return conf, auth_headers, url
+    return conf, auth_header, url
 
 def create_template(template='template.json', image="image.png", studio_url=[], secure_mode=True):
-    # Get STACKn config
+    
     conf = {
         'STACKN_URL': studio_url,
         'STACKN_SECURE': secure_mode
     }
+    
     conf, status = stackn.auth.get_config(conf, required=['STACKN_URL'])
-    auth_headers, conf = get_auth_headers(conf)
-    if not auth_headers:
-        print("Failed to set authentication headers.")
+
+    if not status:
+        print("Failed to get current STACKn configuration file.")
+        return False
+
+    auth_header, conf = get_auth_header(conf)
+    if not auth_header:
         return False
 
     with open(template, 'r') as templ_file:
@@ -190,7 +232,7 @@ def create_template(template='template.json', image="image.png", studio_url=[],
 
     endpoints = get_endpoints(conf['STACKN_URL'])
     url = endpoints['project_templates']
-    r = requests.post(url, headers=auth_headers, files=file_ob, data=payload, verify=conf['STACKN_SECURE']) 
+    r = requests.post(url, headers=auth_header, files=file_ob, data=payload, verify=conf['STACKN_SECURE']) 
 
     if r:
         print("Created template.")
@@ -222,19 +264,17 @@ def create_app(settings="config.json",
                studio_url=[],
                secure_mode=True):
 
-    # Get STACKn config
     conf = {
         'STACKN_URL': studio_url,
         'STACKN_SECURE': secure_mode
     }
     conf, status = stackn.auth.get_config(conf, required=['STACKN_URL'])
-    # if not status:
-    #     print("Missing required input (studio URL, project name).")
-    #     return False
+    if not status:
+         print("Failed to get current STACKn configuration file.")
+         return False
 
-    auth_headers, conf = get_auth_headers(conf)
-    if not auth_headers:
-        print("Failed to set authentication headers.")
+    auth_header, conf = get_auth_header(conf)
+    if not auth_header:
         return False
     
     
@@ -259,7 +299,6 @@ def create_app(settings="config.json",
         priority = config['priority']
     ftable.close()
     
-
     payload = {
         'name': name,
         'slug': slug,
@@ -271,17 +310,9 @@ def create_app(settings="config.json",
         'priority': priority
     }
 
-   
-
     endpoints = get_endpoints(conf['STACKN_URL'])
-
-    # if conf['STACKN_PROJECT']:
-    #     project = get_projects(conf, params={'name': conf['STACKN_PROJECT']})
-    # else:
-    #     print("No project name specified.")
-    #     return False
     url = endpoints['admin']['apps'] #.format(project[0]['id'])
-    r = requests.post(url, headers=auth_headers, files=file_ob, data=payload, verify=conf['STACKN_SECURE']) 
+    r = requests.post(url, headers=auth_header, files=file_ob, data=payload, verify=conf['STACKN_SECURE']) 
 
     os.system('rm {}'.format(chart_uid))
 
@@ -294,15 +325,13 @@ def create_app(settings="config.json",
         print(r.reason)
 
 
-
-
 def create_project(name, 
                    description="",
                    repository="",
                    template="stackn-default",
                    studio_url=[],
                    secure_mode=True):
-    # Get STACKn config
+
     conf = {
         'STACKN_URL': studio_url,
         'STACKN_SECURE': secure_mode
@@ -311,24 +340,21 @@ def create_project(name,
     conf, status = stackn.auth.get_config(conf, required=['STACKN_URL'])
 
     if not status:
-        print("Missing required input (studio URL, project name).")
+        print("Failed to get current STACKn configuration file.")
         return False
 
-    auth_headers, conf = get_auth_headers(conf)
-    if not auth_headers:
-        print("Failed to set authentication headers.")
+    auth_header, conf = get_auth_header(conf)
+    if not auth_header:
         return False
     
     endpoints = get_endpoints(conf['STACKN_URL'])
     url = endpoints['projects']
     data = {'name': name, 'description': description, 'repository': repository, 'template': template}
-    res = requests.post(url, headers=auth_headers, json=data, verify=conf['STACKN_SECURE'])
+    res = requests.post(url, headers=auth_header, json=data, verify=conf['STACKN_SECURE'])
     if res:
         print('Created project: '+name)
         conf['STACKN_PROJECT'] = name
         set_current(conf)
-        # print('Setting {} as the active project.'.format(name))
-        # self.set_project(name)
     else:
         print('Failed to create project.')
         print('Status code: {}'.format(res.status_code))
@@ -343,15 +369,14 @@ def create_resource(filename, studio_url, project, secure):
     conf, status = stackn.auth.get_config(conf, required=['STACKN_URL'])
 
     if not status:
-        print("Missing required input (studio URL, project name).")
+        print("Failed to get current STACKn configuration file.")
         return False
 
-    auth_headers, conf = get_auth_headers(conf)
-    if not auth_headers:
-        print("Failed to set authentication headers.")
+    auth_header, conf = get_auth_header(conf)
+    if not auth_header:
         return False
 
-    project = get_projects(conf=conf['STACKN_URL'], params={'name': conf['STACKN_PROJECT']}, auth_headers=auth_headers)
+    project = get_projects(conf=conf['STACKN_URL'], params={'name': conf['STACKN_PROJECT']}, auth_header=auth_header)
     if len(project)>1:
         print('Found several matching projects.')
         return
@@ -368,7 +393,7 @@ def create_resource(filename, studio_url, project, secure):
     except:
         print("Failed to load JSON data from file {}.".format(filename))
     
-    res = requests.post(url, headers=auth_headers, json=app_data, verify=conf['STACKN_SECURE'])
+    res = requests.post(url, headers=auth_header, json=app_data, verify=conf['STACKN_SECURE'])
     if res:
         print('Created resource.')
     else:
@@ -390,7 +415,6 @@ def create_object(model_name,
 
     """ Publish an object to Studio. """
 
-    # Get STACKn config
     conf = {
         'STACKN_MODEL': model_name,
         'STACKN_URL': studio_url,
@@ -405,15 +429,14 @@ def create_object(model_name,
                                                           'STACKN_PROJECT'])
 
     if not status:
-        print("Missing required input (studio URL, project name).")
+        print("Failed to get current STACKn configuration file.")
         return False
 
-    auth_headers, conf = get_auth_headers(conf)
-    if not auth_headers:
-        print("Failed to set authentication headers.")
+    auth_header, conf = get_auth_header(conf)
+    if not auth_header:
         return False
 
-    project = get_projects(conf=conf, params={'name': conf['STACKN_PROJECT']}, auth_headers=auth_headers)
+    project = get_projects(conf=conf, params={'name': conf['STACKN_PROJECT']}, auth_header=auth_header)
     if len(project)>1:
         print('Found several matching projects.')
         return
@@ -449,7 +472,7 @@ def create_object(model_name,
     else:
         with open(model_card, 'r') as f:
             model_card_html_string = f.read()
-    # # Upload model.
+    
     status = stackn.s3.set_artifact(model_uid,
                                     model_file,
                                     'models',
@@ -461,7 +484,6 @@ def create_object(model_name,
         print("Failed to upload model to S3 storage")
         return False
     
-    # Register model in STACKn
     model_data = {"uid": model_uid,
                   "name": model_name,
                   "release_type": release_type,
@@ -472,7 +494,7 @@ def create_object(model_name,
     endpoints = get_endpoints(conf['STACKN_URL'])
     url = endpoints['models'].format(project['id'])
 
-    r = requests.post(url, json=model_data, headers=auth_headers, verify=secure_mode)
+    r = requests.post(url, json=model_data, headers=auth_header, verify=secure_mode)
     if not _check_status(r, error_msg="Failed to create model."):
         # Delete model object from storage.
         repo.delete_artifact(model_uid)
@@ -482,6 +504,7 @@ def create_object(model_name,
         # Delete temporary archive file.
         os.system('rm {}'.format(model_file))
     print('Released model: {}, release_type: {}'.format(model_name, release_type))
+
     return True
 
 def create_releasename(name, studio_url, project, secure):
@@ -490,11 +513,11 @@ def create_releasename(name, studio_url, project, secure):
         'STACKN_PROJECT': project,
         'STACKN_SECURE': secure
     }
-    conf, auth_headers, url = setup_project_endpoint_call(conf, 'releasenames')
+    conf, auth_header, url = setup_project_endpoint_call(conf, 'releasenames')
     params = {
         "name": name
     }
-    res = requests.post(url, json=params, headers=auth_headers, verify=conf['STACKN_SECURE'])
+    res = requests.post(url, json=params, headers=auth_header, verify=conf['STACKN_SECURE'])
     if res:
         print(res.text)
     else:
@@ -509,7 +532,7 @@ def delete_app(name, studio_url=[], project=[], secure=True):
         "STACKN_PROJECT": project,
         "STACKN_SECURE": secure
     }
-    conf, auth_headers, url = setup_project_endpoint_call(conf, 'appinstances')
+    conf, auth_header, url = setup_project_endpoint_call(conf, 'appinstances')
     params = {
         "name": name
     }
@@ -523,7 +546,7 @@ def delete_app(name, studio_url=[], project=[], secure=True):
     for app in apps:
         ai_id = app['id']
         tmp_url = url+str(ai_id)+'/'
-        res = requests.delete(tmp_url, headers=auth_headers, verify=conf['STACKN_SECURE'])
+        res = requests.delete(tmp_url, headers=auth_header, verify=conf['STACKN_SECURE'])
         if res:
             print("Deleted app: {}".format(name))
         else:
@@ -537,9 +560,13 @@ def delete_app_obj(slug, studio_url=[], secure=True):
         'STACKN_SECURE': secure
     }
     conf, status = stackn.auth.get_config(conf, required=['STACKN_URL'])
-    auth_headers, conf = get_auth_headers(conf)
-    if not auth_headers:
-        print("Failed to set authentication headers.")
+
+    if not status:
+        print("Failed to get current STACKn configuration file.")
+        return False
+
+    auth_header, conf = get_auth_header(conf)
+    if not auth_header:
         return False
 
     payload = {
@@ -548,25 +575,16 @@ def delete_app_obj(slug, studio_url=[], secure=True):
     endpoints = get_endpoints(conf['STACKN_URL'])
     url = endpoints['admin']['apps']
 
-    apps = requests.get(url, headers=auth_headers, params=payload, verify=conf['STACKN_SECURE'])
+    apps = requests.get(url, headers=auth_header, params=payload, verify=conf['STACKN_SECURE'])
     apps = apps.json()
     for app in apps:
         print("Deleting {}, revision {}.".format(app['name'], app['revision']))
         print(url+str(app['id'])+'/')
-        r = requests.delete(url+str(app['id'])+'/', headers=auth_headers, verify=conf['STACKN_SECURE'])
+        r = requests.delete(url+str(app['id'])+'/', headers=auth_header, verify=conf['STACKN_SECURE'])
         print(r.text)
 
     print(url)
- 
-    # r = requests.delete(url, headers=auth_headers, data=payload) 
 
-    # if r:
-    #     print("Created template.")
-    # else:
-    #     print("Failed to create template.")
-    #     print(r.status_code)
-    #     print(r.text)
-    #     print(r.reason)
 
 def delete_object(name, version=None, studio_url=[], project=[], secure=True):
     if version:
@@ -579,14 +597,14 @@ def delete_object(name, version=None, studio_url=[], project=[], secure=True):
         "STACKN_PROJECT": project,
         "STACKN_SECURE": secure
     }
-    conf, auth_headers, url = setup_project_endpoint_call(conf, 'models')
+    conf, auth_header, url = setup_project_endpoint_call(conf, 'models')
     objects = call_project_endpoint('models', conf=conf, params=params)
-    # models  = self.get_models(self.project['id'], params)
+
     if not objects:
         print("No objects found with the given name and/or version.")
     for obj in objects:
         tmp_url = '{}{}/'.format(url, obj['id'])
-        res = requests.delete(tmp_url, headers=auth_headers, verify=conf['STACKN_SECURE'])
+        res = requests.delete(tmp_url, headers=auth_header, verify=conf['STACKN_SECURE'])
         if res:
             print("Deleted object: {}".format(name))
         else:
@@ -600,8 +618,8 @@ def delete_project(name, studio_url=[], secure=True):
         "STACKN_SECURE": secure,
         "STACKN_PROJECT": name
     }
-    conf, auth_headers, url = setup_project_endpoint_call(conf, 'project_del')
-    res = requests.delete(url, headers=auth_headers, verify=conf['STACKN_SECURE'])
+    conf, auth_header, url = setup_project_endpoint_call(conf, 'project_del')
+    res = requests.delete(url, headers=auth_header, verify=conf['STACKN_SECURE'])
     if res:
         print("Deleted project: {}".format(name))
     else:
@@ -615,7 +633,7 @@ def delete_meta_resource(resource_type, name, project=[], studio_url=[], secure=
         "STACKN_URL": studio_url,
         "STACKN_SECURE": secure
     }
-    conf, auth_headers, url = setup_project_endpoint_call(conf, resource_type)
+    conf, auth_header, url = setup_project_endpoint_call(conf, resource_type)
     params = {
         "name": name
     }
@@ -629,7 +647,7 @@ def delete_meta_resource(resource_type, name, project=[], studio_url=[], secure=
     
     env = envs[0]
     url = '{}{}/'.format(url, env['id'])
-    res = requests.delete(url, headers=auth_headers, verify=conf['STACKN_SECURE'])
+    res = requests.delete(url, headers=auth_header, verify=conf['STACKN_SECURE'])
     if res:
         print("Deleted {}: {}".format(resource_type, name))
     else:
@@ -647,4 +665,4 @@ def set_current(conf):
         if 'STACKN_PROJECT' in conf:
             print(conf['STACKN_PROJECT'])
     else:
-        pass
\ No newline at end of file
+        pass

From 2fa36d367de951ee4ee492def1ac8ab971889115 Mon Sep 17 00:00:00 2001
From: TechCarmat <techcarmat@pm.me>
Date: Tue, 16 Nov 2021 17:29:34 +0100
Subject: [PATCH 3/6] Improving with: basic conditional checks, text format and
 sorting

---
 cli/stackn/get.py | 253 +++++++++++++++++++++++++++++-----------------
 1 file changed, 163 insertions(+), 90 deletions(-)

diff --git a/cli/stackn/get.py b/cli/stackn/get.py
index 68a4a27e3..cb5ac6758 100644
--- a/cli/stackn/get.py
+++ b/cli/stackn/get.py
@@ -29,44 +29,33 @@ def _find_dict_by_value(dicts, key, value):
         return []
     return res
 
+
 @main.group('get', cls=AliasedGroup)
 def get():
   pass
 
-@get.command('remote')
-@click.option('--secure/--insecure', required=False, default=True)
-def get_rem(secure):
-    current = get_remote()
-    for curr in current:
-        print(curr)
-
-
 
-@get.command('current')
-@click.option('--secure/--insecure', required=False, default=True)
-def get_curr(secure):
-    current = get_current(secure=secure)
-    if current['STACKN_URL']:
-        print("Studio: {}".format(current['STACKN_URL']))
-        if current['STACKN_PROJECT']:
-            print("Project: {}".format(current['STACKN_PROJECT']))
-        else:
-            print("No project set.")
-    else:
-        print("No STACKn instance set as current.")
 
-@get.command('project')
+@get.command('S3')
+@click.option('-p', '--project', required=False, default=[])
 @click.option('-u', '--studio-url', required=False, default=[])
+@click.option('-n', '--name', required=False, default=[])
 @click.option('--secure/--insecure', required=False, default=True)
-def project(studio_url, secure):
+def s3(project, studio_url, name, secure):
     conf = {
+        'STACKN_PROJECT': project,
         'STACKN_URL': studio_url,
         'STACKN_SECURE': secure
     }
-    projects = get_projects(conf=conf)
-    if projects:
-        _print_table(projects, ['Name', 'Created'], ['name', 'created_at'])
+    params = []
+    if name:
+        params = {"name": name}
+    s3s = call_project_endpoint('s3', params=params, conf=conf)
 
+    if not s3s:
+        return False
+    
+    _print_table(s3s, ['Name', 'Host', 'Region'], ['name', 'host', 'region'])
 
 
 @get.command('app')
@@ -77,6 +66,12 @@ def app(category, secure):
     if category:
         params = {"app__category": category.lower()}
     apps = call_project_endpoint('appinstances', params=params, conf={"STACKN_SECURE": secure})
+
+    # call_project_endpoint can return false for various reasons
+    if not apps:
+        print("Apps could not be fetched.")
+        return False
+
     applist = list()
     for app in apps:
         tmp = dict()
@@ -93,51 +88,42 @@ def app(category, secure):
     applist = sorted(applist, key=lambda k: k['app_cat']) 
 
     _print_table(applist, ['Category', 'App', 'Name', 'URL', 'Status'], ['app_cat', 'app_name', 'name', 'url', 'status'])
-    # print(apps)
 
 
-@get.command('object')
-@click.option('-t', '--object-type', required=False, default="model")
-@click.option('-p', '--project', required=False, default=[])
-@click.option('-u', '--studio-url', required=False, default=[])
+@get.command('current')
 @click.option('--secure/--insecure', required=False, default=True)
-def obj(object_type, project, studio_url, secure):
-    conf = {
-        'STACKN_OBJECT_TYPE': object_type,
-        'STACKN_PROJECT': project,
-        'STACKN_URL': studio_url,
-        'STACKN_SECURE': secure
-    }
-    params = []
-    object_types = call_project_endpoint('objecttypes', conf=conf)
-    if not object_types:
-        return
-    if object_type:
-        obj_type = _find_dict_by_value(object_types, 'slug', object_type)
-        if not obj_type:
-            return
-        params = {'object_type': obj_type['id']}
+def get_curr(secure):
 
-    objects = call_project_endpoint('models', conf=conf, params=params)
+    current = get_current(secure=secure)
 
-    obj_dict = dict()
-    for obj_type in object_types:
-        obj_dict[str(obj_type['id'])] = obj_type['name']
-    for obj in objects:
-        obj['object_type'] = obj_dict[str(obj['object_type'][0])]
-    _print_table(objects, ['Name', 'Version', 'Type', 'Created'], ['name', 'version','object_type', 'uploaded_at'])
+    if not current:
+        return False
+    else:
+        if current['STACKN_URL']:
+            print("Studio: {}".format(current['STACKN_URL']))
+            if current['STACKN_PROJECT']:
+                print("Project: {}".format(current['STACKN_PROJECT']))
+            else:
+                print("No project set.")
+ 
 
 @get.command('environment')
 @click.option('-p', '--project', required=False, default=[])
 @click.option('-u', '--studio-url', required=False, default=[])
 @click.option('--secure/--insecure', required=False, default=True)
 def environment(project, studio_url, secure):
+    
     conf = {
         'STACKN_PROJECT': project,
         'STACKN_URL': studio_url,
         'STACKN_SECURE': secure
     }
+    
     environments = call_project_endpoint('environments', conf=conf)
+    
+    if not environments:
+        return False
+
     envlist = list()
     for env in environments:
         tmp = dict()
@@ -151,45 +137,149 @@ def environment(project, studio_url, secure):
     envlist = sorted(envlist, key=lambda k: k['cat']) 
     _print_table(envlist, header, fields)
 
+
 @get.command('flavor')
 @click.option('-p', '--project', required=False, default=[])
 @click.option('-u', '--studio-url', required=False, default=[])
 @click.option('--secure/--insecure', required=False, default=True)
 def flavor(project, studio_url, secure):
+
     conf = {
         'STACKN_PROJECT': project,
         'STACKN_URL': studio_url,
         'STACKN_SECURE': secure
     }
+
     flavors = call_project_endpoint('flavors', conf=conf)
+    
+    if not flavors:
+        return False
+
     header = ['Name', 'CPU req', 'CPU lim', 'Mem req', 'Mem lim', 'GPUs', 'Eph mem req', 'Eph mem lim']
     fields = ['name', 'cpu_req', 'cpu_lim', 'mem_req', 'mem_lim', 'gpu_req', 'ephmem_req', 'ephmem_lim']
     _print_table(flavors, header, fields)
 
-@get.command('objecttypes')
+
+@get.command('mlflow')
+@click.option('-p', '--project', required=False, default=[])
+@click.option('-u', '--studio-url', required=False, default=[])
+@click.option('--secure/--insecure', required=False, default=True)
+def mlflow(project, studio_url, secure):
+
+    conf = {
+        'STACKN_PROJECT': project,
+        'STACKN_URL': studio_url,
+        'STACKN_SECURE': secure
+    }
+
+    mlflows = call_project_endpoint('mlflow', conf=conf)
+    
+    if not mlflows:
+        return False
+
+    mlflowlist = list()
+    for mlflow in mlflows:
+        tmp = dict()
+        tmp['name'] = mlflow['name']
+        tmp['URL'] = mlflow['mlflow_url']
+        tmp['S3'] = mlflow['s3']['name']
+        mlflowlist.append(tmp)
+    _print_table(mlflowlist, ['Name', 'URL', 'S3'], ['name', 'URL', 'S3'])
+
+
+@get.command('model-object')
+@click.option('-t', '--object-type', required=False, default="model")
+@click.option('-p', '--project', required=False, default=[])
+@click.option('-u', '--studio-url', required=False, default=[])
+@click.option('--secure/--insecure', required=False, default=True)
+def obj(object_type, project, studio_url, secure):
+
+    conf = {
+        'STACKN_OBJECT_TYPE': object_type,
+        'STACKN_PROJECT': project,
+        'STACKN_URL': studio_url,
+        'STACKN_SECURE': secure
+    }
+
+    object_types = call_project_endpoint('objecttypes', conf=conf)
+    if not object_types:
+        return False
+
+    obj_type = _find_dict_by_value(object_types, 'slug', object_type)
+
+    if not obj_type:
+        return
+    
+    params = {'object_type': obj_type['id']}
+    
+    objects = call_project_endpoint('models', conf=conf, params=params)
+
+    if not objects:
+        return False
+
+    obj_dict = dict()
+    for obj_type in object_types:
+        obj_dict[str(obj_type['id'])] = obj_type['name']
+    for obj in objects:
+        obj['object_type'] = obj_dict[str(obj['object_type'][0])]
+    _print_table(objects, ['Name', 'Version', 'Type', 'Created'], ['name', 'version','object_type', 'uploaded_at'])
+
+
+@get.command('model-object-types')
 @click.option('-p', '--project', required=False, default=[])
 @click.option('-u', '--studio-url', required=False, default=[])
 @click.option('--secure/--insecure', required=False, default=True)
-def objecttypes(project, studio_url, secure):
+def objtypes(project, studio_url, secure):
+
     conf = {
         'STACKN_PROJECT': project,
         'STACKN_URL': studio_url,
         'STACKN_SECURE': secure
     }
+
     objecttypes = call_project_endpoint('objecttypes', conf=conf)
+
+    if not objecttypes:
+        return False
+    
     _print_table(objecttypes, ['Name', 'Slug'], ['name', 'slug'])
 
+
+@get.command('project')
+@click.option('-u', '--studio-url', required=False, default=[])
+@click.option('--secure/--insecure', required=False, default=True)
+def project(studio_url, secure):
+
+    conf = {
+        'STACKN_URL': studio_url,
+        'STACKN_SECURE': secure 
+    }
+
+    projects = get_projects(conf=conf)
+    
+    if not projects:
+        return False
+
+    _print_table(projects, ['Name', 'Created'], ['name', 'created_at'])
+
+
 @get.command('releasenames')
 @click.option('-p', '--project', required=False, default=[])
 @click.option('-u', '--studio-url', required=False, default=[])
 @click.option('--secure/--insecure', required=False, default=True)
 def releasenames(project, studio_url, secure):
+
     conf = {
         'STACKN_PROJECT': project,
         'STACKN_URL': studio_url,
         'STACKN_SECURE': secure
     }
+
     objects = call_project_endpoint('releasenames', conf=conf)
+    
+    if not objects:
+        return False
+    
     objlist = list()
     for obj in objects:
         tmp = dict()
@@ -204,42 +294,19 @@ def releasenames(project, studio_url, secure):
     fields = ['name', 'app_name']
     _print_table(objlist, header, fields)
 
-@get.command('S3')
-@click.option('-p', '--project', required=False, default=[])
-@click.option('-u', '--studio-url', required=False, default=[])
-@click.option('-n', '--name', required=False, default=[])
-@click.option('--secure/--insecure', required=False, default=True)
-def s3(project, studio_url, name, secure):
-    conf = {
-        'STACKN_PROJECT': project,
-        'STACKN_URL': studio_url,
-        'STACKN_SECURE': secure
-    }
-    params = []
-    if name:
-        params = {"name": name}
-    s3s = call_project_endpoint('s3', params=params, conf=conf)
-    _print_table(s3s, ['Name', 'Host', 'Region'], ['name', 'host', 'region'])
 
-@get.command('mlflow')
-@click.option('-p', '--project', required=False, default=[])
-@click.option('-u', '--studio-url', required=False, default=[])
+@get.command('remote')
 @click.option('--secure/--insecure', required=False, default=True)
-def mlflow(project, studio_url, secure):
-    conf = {
-        'STACKN_PROJECT': project,
-        'STACKN_URL': studio_url,
-        'STACKN_SECURE': secure
-    }
-    mlflows = call_project_endpoint('mlflow', conf=conf)
-    mlflowlist = list()
-    for mlflow in mlflows:
-        tmp = dict()
-        tmp['name'] = mlflow['name']
-        tmp['URL'] = mlflow['mlflow_url']
-        tmp['S3'] = mlflow['s3']['name']
-        mlflowlist.append(tmp)
-    _print_table(mlflowlist, ['Name', 'URL', 'S3'], ['name', 'URL', 'S3'])
+def get_rem(secure):
+
+    current_remote = get_remote(inp_conf = {'STACKN_SECURE': secure })
+
+    if not current_remote:
+        return False
+    else:
+        for curr in current_remote:
+            print(curr)
+
 
 @get.command('templates')
 @click.option('-u', '--studio-url', required=False, default=[])
@@ -250,6 +317,12 @@ def templates(studio_url, secure):
         'STACKN_SECURE': secure
     }
     templates = call_admin_endpoint('project_templates', conf=conf)
+
+    # call_admin_endpoint can return false for various reasons
+    if not templates:
+        print("Templates could not be fetched.")
+        return False
+
     templateslist = list()
     for template in templates:
         tmp = dict()
@@ -274,4 +347,4 @@ def templates(studio_url, secure):
     "MLflow": mlflow,
     "mlflows": mlflow,
     "MLflows": mlflow
-}
\ No newline at end of file
+}

From dbaf3b953afccac9b295a73f26503c77b3012ec5 Mon Sep 17 00:00:00 2001
From: TechCarmat <techcarmat@pm.me>
Date: Thu, 18 Nov 2021 15:02:15 +0100
Subject: [PATCH 4/6] Adding Help messages for the CLI

---
 cli/stackn/main.py | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/cli/stackn/main.py b/cli/stackn/main.py
index f1f8a12cd..2c75acd56 100644
--- a/cli/stackn/main.py
+++ b/cli/stackn/main.py
@@ -12,8 +12,6 @@
     message='%(prog)s, %(version)s'
 )
 
-@click.group()
+@click.group(help='CLI tool to manage STACKn common tasks. Please note that stackn cli commands are case sensitive.')
 def main():
     pass
-
-

From 76caf54df73ebe894b69810e721190f6b63c83c7 Mon Sep 17 00:00:00 2001
From: TechCarmat <techcarmat@pm.me>
Date: Thu, 18 Nov 2021 15:03:29 +0100
Subject: [PATCH 5/6] Refactoring and improving of get, create and delete CLI
 commands.

---
 cli/stackn/auth.py   |   3 +-
 cli/stackn/create.py | 106 +++++++-------
 cli/stackn/delete.py |  58 ++++----
 cli/stackn/get.py    | 157 +++++++++-----------
 cli/stackn/stackn.py | 339 +++++++++++++++++++++++--------------------
 5 files changed, 330 insertions(+), 333 deletions(-)

diff --git a/cli/stackn/auth.py b/cli/stackn/auth.py
index d695e7581..7e1658d36 100644
--- a/cli/stackn/auth.py
+++ b/cli/stackn/auth.py
@@ -319,8 +319,9 @@ def get_token(conf={}, write_to_file=True):
         if write_to_file:
             write_config(conf)
     else:
-        print('Failed to authenticate with token, please login again.')
+        print('Failed to authenticate with token.')
         print(res.text)
+        print("Please login again by running 'stackn login -u <your-username> -p <your-user-password>'")
         return conf, False
 
     return conf, True
diff --git a/cli/stackn/create.py b/cli/stackn/create.py
index 19b7a4a3e..e7f35c74e 100644
--- a/cli/stackn/create.py
+++ b/cli/stackn/create.py
@@ -1,7 +1,7 @@
 import click
 
 from .main import main
-from .stackn import create_object, create_project, create_resource, create_releasename, create_app, create_apps
+from .stackn import create_object, create_project, create_meta_resource, create_app, create_apps
 from .stackn import create_template, create_templates
 
 class AliasedGroup(click.Group):
@@ -17,56 +17,8 @@ def get_command(self, ctx, cmd_name):
 def create():
   pass
 
-@create.command('project')
-@click.argument('name')
-@click.option('-d', '--description', required=False, default="")
-@click.option('-t', '--template', required=False, default="stackn-default")
-@click.option('-u', '--studio-url', required=False, default=[])
-@click.option('--secure/--insecure', default=True)
-def project(name, description, template, studio_url, secure):
-    create_project(name, description=description, template=template, secure_mode=secure)
-
-@create.command('resource')
-@click.argument('filename')
-@click.option('-u', '--studio-url', required=False, default=[])
-@click.option('-p', '--project', required=False, default=[])
-@click.option('--secure/--insecure', default=True)
-def resource(filename, studio_url, project, secure):
-    create_resource(filename, studio_url=studio_url, project=project, secure=secure)
-
-@create.command('releasename')
-@click.argument('name')
-@click.option('-u', '--studio-url', required=False, default=[])
-@click.option('-p', '--project', required=False, default=[])
-@click.option('--secure/--insecure', default=True)
-def releasename(name, studio_url, project, secure):
-    create_releasename(name, studio_url=studio_url, project=project, secure=secure)
-
-@create.command('object')
-@click.argument('name')
-@click.option('-t', '--object-type', required=False, default="model")
-@click.option('-f', '--file-name', required=False, default="")
-@click.option('-r', '--release-type', required=False, default="minor")
-@click.option('-d', '--description', required=False, default="")
-@click.option('-m', '--model-card', required=False, default="")
-@click.option('-p', '--project', required=False, default=[])
-@click.option('-u', '--studio-url', required=False, default=[])
-@click.option('-s', '--s3-storage', required=False, default=None)
-@click.option('--secure/--insecure', default=True)
-def obj(name, object_type, file_name, release_type, description, model_card, project, studio_url, s3_storage, secure):
-    create_object(name,
-                  model_file=file_name,
-                  project_name=project,
-                  release_type=release_type,
-                  object_type=object_type,
-                  model_description=description,
-                  model_card=model_card,
-                  studio_url=studio_url,
-                  s3storage=s3_storage,
-                  secure_mode=secure)
-
 
-# Admin commands
+# Admin-privileges commands
 
 @create.command('app')
 @click.option('-t', '--settings', required=False, default="config.json")
@@ -107,12 +59,58 @@ def templates(studio_url, secure):
     create_templates(studio_url=studio_url,
                      secure_mode=secure)
 
+
+# Also for non-admin users
+
+@create.command('meta-resource', help="Such as: project environments, flavors, mlflow and s3 enpoints")
+@click.argument('filename')
+@click.option('-u', '--studio-url', required=False, default=[])
+@click.option('-p', '--project', required=False, default=[])
+@click.option('--secure/--insecure', default=True)
+def metaresource(filename, studio_url, project, secure):
+    create_meta_resource(filename, studio_url=studio_url, project=project, secure=secure)
+
+
+@create.command('model-obj')
+@click.argument('name')
+@click.option('-t', '--object-type', required=False, default="model")
+@click.option('-f', '--file-name', required=False, default="")
+@click.option('-r', '--release-type', required=False, default="minor")
+@click.option('-d', '--description', required=False, default="")
+@click.option('-m', '--model-card', required=False, default="")
+@click.option('-p', '--project', required=False, default=[])
+@click.option('-u', '--studio-url', required=False, default=[])
+@click.option('-s', '--s3-storage', required=False, default=None)
+@click.option('--secure/--insecure', default=True)
+def obj(name, object_type, file_name, release_type, description, model_card, project, studio_url, s3_storage, secure):
+    create_object(name,
+                  model_file=file_name,
+                  project_name=project,
+                  release_type=release_type,
+                  object_type=object_type,
+                  model_description=description,
+                  model_card=model_card,
+                  studio_url=studio_url,
+                  s3storage=s3_storage,
+                  secure_mode=secure)
+
+
+@create.command('project')
+@click.argument('name')
+@click.option('-d', '--description', required=False, default="")
+@click.option('-t', '--template', required=False, default="stackn-default")
+@click.option('-u', '--studio-url', required=False, default=[])
+@click.option('--secure/--insecure', default=True)
+def project(name, description, template, studio_url, secure):
+    create_project(name, description=description, template=template, secure_mode=secure)
+
+
 ALIASES = {
     "projects": project,
-    "resources": resource,
+    "resources": metaresource,
     "app": app,
     "objects": obj,
-    "environments": resource,
-    "environment": resource,
-    "flavors": resource
+    "environments": metaresource,
+    "environment": metaresource,
+    "flavors": metaresource
 }
\ No newline at end of file
diff --git a/cli/stackn/delete.py b/cli/stackn/delete.py
index 36a4c2396..213afcbaa 100644
--- a/cli/stackn/delete.py
+++ b/cli/stackn/delete.py
@@ -1,6 +1,7 @@
 import click
 from .main import main
-from .stackn import delete_app, delete_object, delete_project, delete_meta_resource, delete_app_obj
+from .stackn import delete_app, delete_object, delete_project,  delete_meta_resource
+
 
 class AliasedGroup(click.Group):
     def get_command(self, ctx, cmd_name):
@@ -14,12 +15,6 @@ def get_command(self, ctx, cmd_name):
 def delete():
   pass
 
-@delete.command('project')
-@click.argument('name')
-@click.option('-u', '--studio-url', required=False, default=[])
-@click.option('--secure/--insecure', default=True)
-def delete_proj(name, studio_url, secure):
-    delete_project(name, studio_url=studio_url, secure=secure)
 
 @delete.command('app')
 @click.argument('name')
@@ -29,25 +24,6 @@ def delete_proj(name, studio_url, secure):
 def app(name, studio_url, project, secure):
     delete_app(name, studio_url, project, secure)
 
-@delete.command('appobj')
-@click.argument('slug')
-@click.option('-u', '--studio-url', required=False, default=[])
-@click.option('--secure/--insecure', default=True)
-def app(slug, studio_url, secure):
-    delete_app_obj(slug, studio_url=studio_url, secure=secure)
-
-@delete.command('object')
-@click.argument('name')
-@click.option('-v', '--version', required=False, default=[])
-@click.option('-u', '--studio-url', required=False, default=[])
-@click.option('-p', '--project', required=False, default=[])
-@click.option('--secure/--insecure', default=True)
-def delete_obj(name, version, studio_url, project, secure):
-    delete_object(name,
-                  version=version,
-                  studio_url=studio_url,
-                  project=project,
-                  secure=secure)
 
 @delete.command('environment')
 @click.argument('name')
@@ -57,6 +33,7 @@ def delete_obj(name, version, studio_url, project, secure):
 def delete_env(name, project, studio_url, secure):
     delete_meta_resource('environments', name, project=project, studio_url=studio_url, secure=secure)
 
+
 @delete.command('flavor')
 @click.argument('name')
 @click.option('-p', '--project', required=False, default=[])
@@ -65,7 +42,8 @@ def delete_env(name, project, studio_url, secure):
 def delete_flavor(name, project, studio_url, secure):
     delete_meta_resource('flavors', name, project=project, studio_url=studio_url, secure=secure)
 
-@delete.command('MLflow')
+
+@delete.command('mlflow')
 @click.argument('name')
 @click.option('-p', '--project', required=False, default=[])
 @click.option('-u', '--studio-url', required=False, default=[])
@@ -73,21 +51,37 @@ def delete_flavor(name, project, studio_url, secure):
 def delete_mlflow(name, project, studio_url, secure):
     delete_meta_resource('mlflow', name, project=project, studio_url=studio_url, secure=secure)
 
-@delete.command('S3')
+
+@delete.command('model-obj')
 @click.argument('name')
+@click.option('-v', '--version', required=False, default=[])
+@click.option('-u', '--studio-url', required=False, default=[])
 @click.option('-p', '--project', required=False, default=[])
+@click.option('--secure/--insecure', default=True)
+def delete_obj(name, version, studio_url, project, secure):
+    delete_object(name,
+                  version=version,
+                  studio_url=studio_url,
+                  project=project,
+                  secure=secure)
+
+
+@delete.command('project')
+@click.argument('name')
 @click.option('-u', '--studio-url', required=False, default=[])
 @click.option('--secure/--insecure', default=True)
-def delete_s3(name, project, studio_url, secure):
-    delete_meta_resource('s3', name, project=project, studio_url=studio_url, secure=secure)
+def delete_proj(name, studio_url, secure):
+    delete_project(name, studio_url=studio_url, secure=secure)
+
 
-@delete.command('releasename')
+@delete.command('s3')
 @click.argument('name')
 @click.option('-p', '--project', required=False, default=[])
 @click.option('-u', '--studio-url', required=False, default=[])
 @click.option('--secure/--insecure', default=True)
 def delete_s3(name, project, studio_url, secure):
-    delete_meta_resource('releasenames', name, project=project, studio_url=studio_url, secure=secure)
+    delete_meta_resource('s3', name, project=project, studio_url=studio_url, secure=secure)
+
 
 ALIASES = {
     "projects": delete_proj,
diff --git a/cli/stackn/get.py b/cli/stackn/get.py
index cb5ac6758..8594763d3 100644
--- a/cli/stackn/get.py
+++ b/cli/stackn/get.py
@@ -30,34 +30,11 @@ def _find_dict_by_value(dicts, key, value):
     return res
 
 
-@main.group('get', cls=AliasedGroup)
+@main.group('get', cls=AliasedGroup, help="CLI commands for common create tasks")
 def get():
   pass
 
 
-
-@get.command('S3')
-@click.option('-p', '--project', required=False, default=[])
-@click.option('-u', '--studio-url', required=False, default=[])
-@click.option('-n', '--name', required=False, default=[])
-@click.option('--secure/--insecure', required=False, default=True)
-def s3(project, studio_url, name, secure):
-    conf = {
-        'STACKN_PROJECT': project,
-        'STACKN_URL': studio_url,
-        'STACKN_SECURE': secure
-    }
-    params = []
-    if name:
-        params = {"name": name}
-    s3s = call_project_endpoint('s3', params=params, conf=conf)
-
-    if not s3s:
-        return False
-    
-    _print_table(s3s, ['Name', 'Host', 'Region'], ['name', 'host', 'region'])
-
-
 @get.command('app')
 @click.option('-c', '--category', required=False, default=[])
 @click.option('--secure/--insecure', required=False, default=True)
@@ -68,9 +45,12 @@ def app(category, secure):
     apps = call_project_endpoint('appinstances', params=params, conf={"STACKN_SECURE": secure})
 
     # call_project_endpoint can return false for various reasons
-    if not apps:
+    if apps == False:
         print("Apps could not be fetched.")
         return False
+    elif len(apps) == 0:
+        print("There are no apps associated with the current project.")
+        return
 
     applist = list()
     for app in apps:
@@ -89,6 +69,8 @@ def app(category, secure):
 
     _print_table(applist, ['Category', 'App', 'Name', 'URL', 'Status'], ['app_cat', 'app_name', 'name', 'url', 'status'])
 
+    
+
 
 @get.command('current')
 @click.option('--secure/--insecure', required=False, default=True)
@@ -121,8 +103,11 @@ def environment(project, studio_url, secure):
     
     environments = call_project_endpoint('environments', conf=conf)
     
-    if not environments:
+    if environments == False:
         return False
+    elif len(environments) == 0:
+        print("There are no environments associated with the current project")
+        return
 
     envlist = list()
     for env in environments:
@@ -135,6 +120,7 @@ def environment(project, studio_url, secure):
     header = ['Category', 'App', 'Name', 'Image']
     fields = ['cat', 'app_name', 'name', 'image']
     envlist = sorted(envlist, key=lambda k: k['cat']) 
+
     _print_table(envlist, header, fields)
 
 
@@ -152,11 +138,15 @@ def flavor(project, studio_url, secure):
 
     flavors = call_project_endpoint('flavors', conf=conf)
     
-    if not flavors:
+    if flavors == False:
         return False
+    elif len(flavors) == 0:
+        print("No flavors are associated to the current project.")
+        return
 
     header = ['Name', 'CPU req', 'CPU lim', 'Mem req', 'Mem lim', 'GPUs', 'Eph mem req', 'Eph mem lim']
     fields = ['name', 'cpu_req', 'cpu_lim', 'mem_req', 'mem_lim', 'gpu_req', 'ephmem_req', 'ephmem_lim']
+
     _print_table(flavors, header, fields)
 
 
@@ -174,9 +164,12 @@ def mlflow(project, studio_url, secure):
 
     mlflows = call_project_endpoint('mlflow', conf=conf)
     
-    if not mlflows:
+    if mlflows == False:
         return False
-
+    elif len(mlflows) == 0:
+        print("No MLflows endpoints are associated to the current project.")
+        return
+    
     mlflowlist = list()
     for mlflow in mlflows:
         tmp = dict()
@@ -184,10 +177,11 @@ def mlflow(project, studio_url, secure):
         tmp['URL'] = mlflow['mlflow_url']
         tmp['S3'] = mlflow['s3']['name']
         mlflowlist.append(tmp)
+    
     _print_table(mlflowlist, ['Name', 'URL', 'S3'], ['name', 'URL', 'S3'])
 
 
-@get.command('model-object')
+@get.command('model-obj')
 @click.option('-t', '--object-type', required=False, default="model")
 @click.option('-p', '--project', required=False, default=[])
 @click.option('-u', '--studio-url', required=False, default=[])
@@ -202,6 +196,7 @@ def obj(object_type, project, studio_url, secure):
     }
 
     object_types = call_project_endpoint('objecttypes', conf=conf)
+
     if not object_types:
         return False
 
@@ -214,35 +209,19 @@ def obj(object_type, project, studio_url, secure):
     
     objects = call_project_endpoint('models', conf=conf, params=params)
 
-    if not objects:
+    if objects == False:
         return False
+    elif len(objects) == 0:
+        print("No model objects are associated to the current project")
+        return
 
     obj_dict = dict()
     for obj_type in object_types:
         obj_dict[str(obj_type['id'])] = obj_type['name']
     for obj in objects:
         obj['object_type'] = obj_dict[str(obj['object_type'][0])]
-    _print_table(objects, ['Name', 'Version', 'Type', 'Created'], ['name', 'version','object_type', 'uploaded_at'])
-
 
-@get.command('model-object-types')
-@click.option('-p', '--project', required=False, default=[])
-@click.option('-u', '--studio-url', required=False, default=[])
-@click.option('--secure/--insecure', required=False, default=True)
-def objtypes(project, studio_url, secure):
-
-    conf = {
-        'STACKN_PROJECT': project,
-        'STACKN_URL': studio_url,
-        'STACKN_SECURE': secure
-    }
-
-    objecttypes = call_project_endpoint('objecttypes', conf=conf)
-
-    if not objecttypes:
-        return False
-    
-    _print_table(objecttypes, ['Name', 'Slug'], ['name', 'slug'])
+    _print_table(objects, ['Name', 'Version', 'Type', 'Created'], ['name', 'version','object_type', 'uploaded_at'])
 
 
 @get.command('project')
@@ -257,42 +236,41 @@ def project(studio_url, secure):
 
     projects = get_projects(conf=conf)
     
-    if not projects:
+    if projects == False:
         return False
-
+    elif len(projects) == 0:
+        print("There are no projects associated to the current user.")
+        return
+    
     _print_table(projects, ['Name', 'Created'], ['name', 'created_at'])
 
 
-@get.command('releasenames')
-@click.option('-p', '--project', required=False, default=[])
+@get.command('project-templates')
 @click.option('-u', '--studio-url', required=False, default=[])
 @click.option('--secure/--insecure', required=False, default=True)
-def releasenames(project, studio_url, secure):
-
+def templates(studio_url, secure):
     conf = {
-        'STACKN_PROJECT': project,
         'STACKN_URL': studio_url,
         'STACKN_SECURE': secure
     }
+    templates = call_admin_endpoint('project_templates', conf=conf)
 
-    objects = call_project_endpoint('releasenames', conf=conf)
-    
-    if not objects:
+    # call_admin_endpoint can return false for various reasons
+    if templates == False:
+        print("Templates could not be fetched.")
         return False
-    
-    objlist = list()
-    for obj in objects:
+    elif len(templates) == 0:
+        print("There are no templates.")
+        return
+
+    templateslist = list()
+    for template in templates:
         tmp = dict()
-        tmp['name'] = obj['name']
-        if obj['app']:
-            tmp['app_name'] = obj['app']['name']
-        else:
-            tmp['app_name'] = ''
-        objlist.append(tmp)
-    objlist = sorted(objlist, key=lambda k: k['name'])
-    header = ['Name', 'App']
-    fields = ['name', 'app_name']
-    _print_table(objlist, header, fields)
+        tmp['name'] = template['name']
+        tmp['description'] = template['description']
+        templateslist.append(tmp)
+
+    _print_table(templateslist, ['Name', 'Description'], ['name', 'description'])
 
 
 @get.command('remote')
@@ -308,32 +286,37 @@ def get_rem(secure):
             print(curr)
 
 
-@get.command('templates')
+@get.command('s3')
+@click.option('-p', '--project', required=False, default=[])
 @click.option('-u', '--studio-url', required=False, default=[])
+@click.option('-n', '--name', required=False, default=[])
 @click.option('--secure/--insecure', required=False, default=True)
-def templates(studio_url, secure):
+def s3(project, studio_url, name, secure):
     conf = {
+        'STACKN_PROJECT': project,
         'STACKN_URL': studio_url,
         'STACKN_SECURE': secure
     }
-    templates = call_admin_endpoint('project_templates', conf=conf)
+    params = []
+    if name:
+        params = {"name": name}
+    
+    s3s = call_project_endpoint('s3', params=params, conf=conf)
 
-    # call_admin_endpoint can return false for various reasons
-    if not templates:
-        print("Templates could not be fetched.")
+    if s3s == False:
         return False
+    elif len(s3s) == 0:
+        print("There are no S3 endpoints associated with the current project.")
+        return
+    else:
+        _print_table(s3s, ['Name', 'Host', 'Region'], ['name', 'host', 'region'])
 
-    templateslist = list()
-    for template in templates:
-        tmp = dict()
-        tmp['name'] = template['name']
-        tmp['description'] = template['description']
-        templateslist.append(tmp)
-    _print_table(templateslist, ['Name', 'Description'], ['name', 'description'])
 
 ALIASES = {
     "projects": project,
     "proj": project,
+    "template": templates,
+    "tmpl": templates,
     "apps": app,
     "objects": obj,
     "model": obj,
diff --git a/cli/stackn/stackn.py b/cli/stackn/stackn.py
index 83c3195e2..716db2364 100644
--- a/cli/stackn/stackn.py
+++ b/cli/stackn/stackn.py
@@ -19,92 +19,7 @@ def _check_status(r, error_msg="Failed"):
     else:
         return True
 
-
-def get_endpoints(studio_url):
-    # These endpoints are related to the API available in Studio under: stackn/components/studio/api/
-    endpoints = dict()
-    if (not 'http://' in studio_url) and (not 'https://' in studio_url):
-        studio_url = 'https://'+studio_url
-    base = studio_url.strip('/')+'/api'
-    endpoints['models'] = base+'/projects/{}/models/'
-    endpoints['resources'] = base+'/projects/{}/resources/'
-    endpoints['appinstances'] = base+'/projects/{}/appinstances/'
-    endpoints['objecttypes'] = base+'/projects/{}/objecttype'
-    endpoints['members'] = base+'/projects/{}/members/'
-    endpoints['flavors'] = base+'/projects/{}/flavors/'
-    endpoints['environments'] = base+'/projects/{}/environments/'
-    endpoints['s3'] = base+'/projects/{}/s3/'
-    endpoints['mlflow'] = base+'/projects/{}/mlflow/'
-    endpoints['project_del'] = base+'/projects/{}'
-    endpoints['releasenames'] = base+'/projects/{}/releasenames/'
-    endpoints['projects'] = base+'/projects/'
-    endpoints['project_templates'] = base+'/projecttemplates/'
-    endpoints['admin'] = dict()
-    endpoints['admin']['apps'] = base+'/apps/'
-    return endpoints
-
-def get_auth_header(conf):
-    conf, status = stackn.auth.get_token(conf)
-    if not status:
-        return False, False
-    auth_header = {"Authorization": "Token {}".format(conf['STACKN_ACCESS_TOKEN'])}
-    return auth_header, conf
-
-def get_remote(inp_conf):
-    
-    conf, status = stackn.auth.get_config(inp_conf)
-
-    if not status:
-        return False
-
-    keys = stackn.auth._get_remote(conf)
-    
-    if not keys:
-        return False
-
-    return keys
-
-def get_current(secure):
-
-    res = {'STACKN_URL': False, 'STACKN_PROJECT': False}
-    conf, status = stackn.auth.get_config({'STACKN_SECURE': secure})
-
-    if not status:
-        print("Failed to get current STACKn configuration file.")
-    else:
-        if 'STACKN_URL' in conf:
-            res['STACKN_URL'] = conf['STACKN_URL']
-        if 'STACKN_PROJECT' in conf:
-            res['STACKN_PROJECT'] = conf['STACKN_PROJECT']
-    
-    return res
-
-def get_projects(conf={}, params=[], auth_header=[]):
-    conf, status = stackn.auth.get_config(conf)
-
-    if not status:
-        print('Cannot list projects.')
-        return False
-
-    auth_header, conf = get_auth_header(conf)
-
-    if not auth_header:
-        return False
-
-    endpoints = get_endpoints(conf['STACKN_URL'])
-    url = endpoints['projects']
-    if params:
-        r = requests.get(url, headers=auth_header, params=params, verify=conf['STACKN_SECURE'])
-    else:
-        r = requests.get(url, headers=auth_header, verify=conf['STACKN_SECURE'])
-    if r:
-        projects = json.loads(r.content)
-        return projects
-    else:
-        print("Fetching projects failed.")
-        print('Returned status code: {}'.format(r.status_code))
-        print('Reason: {}'.format(r.reason))
-        return None
+# Sort of utils functions
 
 def call_admin_endpoint(name, conf={}, params=[]):
     
@@ -135,7 +50,9 @@ def call_admin_endpoint(name, conf={}, params=[]):
         print('Reason: {}'.format(r.reason))
         return None
 
+
 def call_project_endpoint(name, conf={}, params=[]):
+
     conf, status = stackn.auth.get_config(conf)
 
     if not status:
@@ -159,10 +76,12 @@ def call_project_endpoint(name, conf={}, params=[]):
         print('Found several matching projects.')
         return
     if not project:
-        print("Project {} not found.".format(conf['STACKN_PROJECT']))
+        print("Project \'{}\' not found.".format(conf['STACKN_PROJECT']))
         return False
+    
     project = project[0]
     url = endpoints[name].format(project['id'])
+
     if params:
         r = requests.get(url, headers=auth_header, params=params, verify=conf['STACKN_SECURE'])
     else:
@@ -176,30 +95,126 @@ def call_project_endpoint(name, conf={}, params=[]):
         print('Reason: {}'.format(r.reason))
         return None
 
+
 def setup_project_endpoint_call(conf, endpoint_type):
+
     conf, status = stackn.auth.get_config(conf, required=['STACKN_URL'])
 
     if not status:
-        print("Missing required input (studio URL, project name).")
-        return False
+        print("Failed to get current STACKn configuration file.")
+        return False, False, False
 
     auth_header, conf = get_auth_header(conf)
     if not auth_header:
-        return False
+        return False, False, False
 
-    project = get_projects(conf=conf['STACKN_URL'], params={'name': conf['STACKN_PROJECT']}, auth_header=auth_header)
-    if len(project)>1:
-        print('Found several matching projects.')
-        return
-    if not project:
-        print("Project {} not found.".format(conf['STACKN_PROJECT']))
-        return False
+    project = get_projects(conf, params={'name': conf['STACKN_PROJECT']}, auth_header=auth_header)
+
+    if project == False:
+        return False, False, False
+    elif len(project)>1:
+        print('Found several matching projects. Please select a specific project.')
+        return False, False, False
+    elif len(project)==0:
+        print("Project \'{}\' not found.".format(conf['STACKN_PROJECT']))
+        return False, False, False
 
     project = project[0]
     endpoints = get_endpoints(conf['STACKN_URL'])
     url = endpoints[endpoint_type].format(project['id'])
     return conf, auth_header, url
 
+# Get functions
+
+def get_endpoints(studio_url):
+    # These endpoints are related to the API available in Studio under: stackn/components/studio/api/
+    endpoints = dict()
+    if (not 'http://' in studio_url) and (not 'https://' in studio_url):
+        studio_url = 'https://'+studio_url
+    base = studio_url.strip('/')+'/api'
+    endpoints['models'] = base+'/projects/{}/models/'
+    endpoints['resources'] = base+'/projects/{}/resources/'
+    endpoints['appinstances'] = base+'/projects/{}/appinstances/'
+    endpoints['members'] = base+'/projects/{}/members/'
+    endpoints['flavors'] = base+'/projects/{}/flavors/'
+    endpoints['environments'] = base+'/projects/{}/environments/'
+    endpoints['s3'] = base+'/projects/{}/s3/'
+    endpoints['mlflow'] = base+'/projects/{}/mlflow/'
+    endpoints['project_del'] = base+'/projects/{}'
+    endpoints['projects'] = base+'/projects/'
+    endpoints['project_templates'] = base+'/projecttemplates/'
+    endpoints['admin'] = dict()
+    endpoints['admin']['apps'] = base+'/apps/'
+    return endpoints
+
+def get_auth_header(conf):
+    conf, status = stackn.auth.get_token(conf)
+    if not status:
+        return False, False
+    auth_header = {"Authorization": "Token {}".format(conf['STACKN_ACCESS_TOKEN'])}
+    return auth_header, conf
+
+def get_current(secure):
+
+    res = {'STACKN_URL': False, 'STACKN_PROJECT': False}
+    conf, status = stackn.auth.get_config({'STACKN_SECURE': secure})
+
+    if not status:
+        print("Failed to get current STACKn configuration file.")
+    else:
+        if 'STACKN_URL' in conf:
+            res['STACKN_URL'] = conf['STACKN_URL']
+        if 'STACKN_PROJECT' in conf:
+            res['STACKN_PROJECT'] = conf['STACKN_PROJECT']
+    
+    return res
+
+def get_projects(conf={}, params=[], auth_header=[]):
+
+    conf, status = stackn.auth.get_config(conf)
+
+    if not status:
+        print('Cannot list projects.')
+        return False
+
+    auth_header, conf = get_auth_header(conf)
+
+    if not auth_header:
+        return False
+
+    endpoints = get_endpoints(conf['STACKN_URL'])
+    url = endpoints['projects']
+    if params:
+        r = requests.get(url, headers=auth_header, params=params, verify=conf['STACKN_SECURE'])
+    else:
+        r = requests.get(url, headers=auth_header, verify=conf['STACKN_SECURE'])
+    if r:
+        projects = json.loads(r.content)
+        return projects
+    else:
+        print("Fetching projects failed.")
+        print('Returned status code: {}'.format(r.status_code))
+        print('Reason: {}'.format(r.reason))
+        return None
+
+
+def get_remote(inp_conf):
+    
+    conf, status = stackn.auth.get_config(inp_conf)
+
+    if not status:
+        return False
+
+    keys = stackn.auth._get_remote(conf)
+    
+    if not keys:
+        return False
+
+    return keys
+
+
+# Create functions
+
 def create_template(template='template.json', image="image.png", studio_url=[], secure_mode=True):
     
     conf = {
@@ -242,6 +257,7 @@ def create_template(template='template.json', image="image.png", studio_url=[],
         print(r.text)
         print(r.reason)
 
+
 def create_templates(folder='.', studio_url=[], secure_mode=True):
     subfolders = [f.path for f in os.scandir(folder) if f.is_dir()]
     curr_dir = os.getcwd()
@@ -250,6 +266,7 @@ def create_templates(folder='.', studio_url=[], secure_mode=True):
         create_template(studio_url=studio_url, secure_mode=secure_mode)
         os.chdir(curr_dir)
 
+
 def create_apps(folder='.', studio_url=[], secure_mode=True):
     subfolders = [f.path for f in os.scandir(folder) if f.is_dir()]
     curr_dir = os.getcwd()
@@ -258,6 +275,7 @@ def create_apps(folder='.', studio_url=[], secure_mode=True):
         create_app(studio_url=studio_url, secure_mode=secure_mode)
         os.chdir(curr_dir)
 
+
 def create_app(settings="config.json",
                chart_archive="chart",
                logo="logo.png",
@@ -360,7 +378,8 @@ def create_project(name,
         print('Status code: {}'.format(res.status_code))
         print(res.text)
 
-def create_resource(filename, studio_url, project, secure):
+
+def create_meta_resource(filename, studio_url, project, secure):
     conf = {
         'STACKN_URL': studio_url,
         'STACKN_PROJECT': project,
@@ -401,6 +420,7 @@ def create_resource(filename, studio_url, project, secure):
         print('Status code: {}'.format(res.status_code))
         print(res.text)
 
+
 def create_object(model_name,
                   studio_url=[],
                   model_file="",
@@ -507,39 +527,35 @@ def create_object(model_name,
 
     return True
 
-def create_releasename(name, studio_url, project, secure):
-    conf = {
-        'STACKN_URL': studio_url,
-        'STACKN_PROJECT': project,
-        'STACKN_SECURE': secure
-    }
-    conf, auth_header, url = setup_project_endpoint_call(conf, 'releasenames')
-    params = {
-        "name": name
-    }
-    res = requests.post(url, json=params, headers=auth_header, verify=conf['STACKN_SECURE'])
-    if res:
-        print(res.text)
-    else:
-        print("Failed to create release name.")
-        print('Status code: {}'.format(res.status_code))
-        print(res.text)
 
+# Delete functions
 
 def delete_app(name, studio_url=[], project=[], secure=True):
+    
     conf = {
         "STACKN_URL": studio_url,
         "STACKN_PROJECT": project,
         "STACKN_SECURE": secure
     }
+
     conf, auth_header, url = setup_project_endpoint_call(conf, 'appinstances')
+    
+    if not conf or not auth_header or not url:
+        print("Failed to set up project API endpoint call.")
+        return False
+
     params = {
         "name": name
     }
-    apps = call_project_endpoint('appinstances', params=params)
+
+    apps = call_project_endpoint('appinstances', conf, params=params)
+
+    if not apps:
+        return False
+    
     if len(apps) > 1:
         print("Found multiple apps with that name, deleting all...")
-    if len(apps) == 0:
+    elif len(apps) == 0:
         print("Found no app with that name, aborting...")
         return False
 
@@ -554,37 +570,6 @@ def delete_app(name, studio_url=[], project=[], secure=True):
             print('Status code: {}'.format(res.status_code))
             print(res.text)
 
-def delete_app_obj(slug, studio_url=[], secure=True):
-    conf = {
-        'STACKN_URL': studio_url,
-        'STACKN_SECURE': secure
-    }
-    conf, status = stackn.auth.get_config(conf, required=['STACKN_URL'])
-
-    if not status:
-        print("Failed to get current STACKn configuration file.")
-        return False
-
-    auth_header, conf = get_auth_header(conf)
-    if not auth_header:
-        return False
-
-    payload = {
-        'slug': slug
-    }
-    endpoints = get_endpoints(conf['STACKN_URL'])
-    url = endpoints['admin']['apps']
-
-    apps = requests.get(url, headers=auth_header, params=payload, verify=conf['STACKN_SECURE'])
-    apps = apps.json()
-    for app in apps:
-        print("Deleting {}, revision {}.".format(app['name'], app['revision']))
-        print(url+str(app['id'])+'/')
-        r = requests.delete(url+str(app['id'])+'/', headers=auth_header, verify=conf['STACKN_SECURE'])
-        print(r.text)
-
-    print(url)
-
 
 def delete_object(name, version=None, studio_url=[], project=[], secure=True):
     if version:
@@ -597,29 +582,47 @@ def delete_object(name, version=None, studio_url=[], project=[], secure=True):
         "STACKN_PROJECT": project,
         "STACKN_SECURE": secure
     }
+
     conf, auth_header, url = setup_project_endpoint_call(conf, 'models')
+
+    if not conf or not auth_header or not url:
+        print("Failed to setup project API endpoint.")
+        return False
+
     objects = call_project_endpoint('models', conf=conf, params=params)
 
-    if not objects:
-        print("No objects found with the given name and/or version.")
+    if objects == False:
+        return False
+    elif len(objects) == 0:
+        print("No model objects found with the given name and/or version.")
+
     for obj in objects:
         tmp_url = '{}{}/'.format(url, obj['id'])
         res = requests.delete(tmp_url, headers=auth_header, verify=conf['STACKN_SECURE'])
         if res:
-            print("Deleted object: {}".format(name))
+            print("Deleted model object: {}".format(name))
         else:
-            print("Failed to delete object.")
+            print("Failed to delete model object.")
             print('Status code: {}'.format(res.status_code))
             print(res.text)
 
+
 def delete_project(name, studio_url=[], secure=True):
+    
     conf = {
         "STACKN_URL": studio_url,
         "STACKN_SECURE": secure,
         "STACKN_PROJECT": name
     }
+    
     conf, auth_header, url = setup_project_endpoint_call(conf, 'project_del')
+
+    if not conf or not auth_header or not url:
+        print("Failed to set up project API endpoint")
+        return False
+    
     res = requests.delete(url, headers=auth_header, verify=conf['STACKN_SECURE'])
+    
     if res:
         print("Deleted project: {}".format(name))
     else:
@@ -627,26 +630,43 @@ def delete_project(name, studio_url=[], secure=True):
         print('Status code: {}'.format(res.status_code))
         print(res.text)
 
+
 def delete_meta_resource(resource_type, name, project=[], studio_url=[], secure=True):
+
     conf = {
         "STACKN_PROJECT": project,
         "STACKN_URL": studio_url,
         "STACKN_SECURE": secure
     }
+
     conf, auth_header, url = setup_project_endpoint_call(conf, resource_type)
+
+    if not conf or not auth_header or not url:
+        print("Failed to setup project API endpoint.")
+        return False
+    
     params = {
         "name": name
     }
-    envs = call_project_endpoint(resource_type, conf=conf, params=params)
-    if len(envs) == 0:
-        print("Didn't find environment with that name.")
+
+    prj_endpts = call_project_endpoint(resource_type, conf=conf, params=params)
+    
+    if prj_endpts == False:
+        return False
+    elif len(prj_endpts) == 0:
+        print("No {}: \'{}\' associated with the current project".format(resource_type, name))
         return False
-    elif len(envs) > 1:
-        print("Found multiple environments with that name.")
+    elif len(prj_endpts) > 1:
+        print("Found multiple resources with the passed name.")
         return False
     
-    env = envs[0]
-    url = '{}{}/'.format(url, env['id'])
+    endpt = prj_endpts[0]
+
+    if resource_type == "mlflow" or resource_type == "s3":
+        url = '{}{}/'.format(url, endpt['name'])
+    else:    
+        url = '{}{}/'.format(url, endpt['id'])
+
     res = requests.delete(url, headers=auth_header, verify=conf['STACKN_SECURE'])
     if res:
         print("Deleted {}: {}".format(resource_type, name))
@@ -655,6 +675,7 @@ def delete_meta_resource(resource_type, name, project=[], studio_url=[], secure=
         print('Status code: {}'.format(res.status_code))
         print(res.text)
 
+# Set Function
 
 def set_current(conf):
     res = stackn.auth._set_current(conf)

From 6b4c5d8fc2dab0baf706b2c7aae6cb04734ef36c Mon Sep 17 00:00:00 2001
From: TechCarmat <techcarmat@pm.me>
Date: Thu, 18 Nov 2021 15:41:56 +0100
Subject: [PATCH 6/6] Re-adding needed endpoint for model-obj

---
 cli/stackn/get.py    |  7 ++++---
 cli/stackn/stackn.py | 16 +++++++++-------
 2 files changed, 13 insertions(+), 10 deletions(-)

diff --git a/cli/stackn/get.py b/cli/stackn/get.py
index 8594763d3..73ec216f6 100644
--- a/cli/stackn/get.py
+++ b/cli/stackn/get.py
@@ -30,7 +30,7 @@ def _find_dict_by_value(dicts, key, value):
     return res
 
 
-@main.group('get', cls=AliasedGroup, help="CLI commands for common create tasks")
+@main.group('get', cls=AliasedGroup)
 def get():
   pass
 
@@ -197,12 +197,13 @@ def obj(object_type, project, studio_url, secure):
 
     object_types = call_project_endpoint('objecttypes', conf=conf)
 
-    if not object_types:
+    if object_types == False:
         return False
 
     obj_type = _find_dict_by_value(object_types, 'slug', object_type)
 
     if not obj_type:
+        print("No model objects found for this project.")
         return
     
     params = {'object_type': obj_type['id']}
@@ -212,7 +213,7 @@ def obj(object_type, project, studio_url, secure):
     if objects == False:
         return False
     elif len(objects) == 0:
-        print("No model objects are associated to the current project")
+        print("No model objects are associated to the current project.")
         return
 
     obj_dict = dict()
diff --git a/cli/stackn/stackn.py b/cli/stackn/stackn.py
index 716db2364..f42c65681 100644
--- a/cli/stackn/stackn.py
+++ b/cli/stackn/stackn.py
@@ -132,19 +132,21 @@ def get_endpoints(studio_url):
     if (not 'http://' in studio_url) and (not 'https://' in studio_url):
         studio_url = 'https://'+studio_url
     base = studio_url.strip('/')+'/api'
-    endpoints['models'] = base+'/projects/{}/models/'
-    endpoints['resources'] = base+'/projects/{}/resources/'
+    endpoints['admin'] = dict()
+    endpoints['admin']['apps'] = base+'/apps/'
     endpoints['appinstances'] = base+'/projects/{}/appinstances/'
-    endpoints['members'] = base+'/projects/{}/members/'
-    endpoints['flavors'] = base+'/projects/{}/flavors/'
     endpoints['environments'] = base+'/projects/{}/environments/'
-    endpoints['s3'] = base+'/projects/{}/s3/'
+    endpoints['flavors'] = base+'/projects/{}/flavors/'
+    endpoints['members'] = base+'/projects/{}/members/'
+    endpoints['models'] = base+'/projects/{}/models/'
     endpoints['mlflow'] = base+'/projects/{}/mlflow/'
+    endpoints['objecttypes'] = base+'/projects/{}/objecttype'
     endpoints['project_del'] = base+'/projects/{}'
     endpoints['projects'] = base+'/projects/'
     endpoints['project_templates'] = base+'/projecttemplates/'
-    endpoints['admin'] = dict()
-    endpoints['admin']['apps'] = base+'/apps/'
+    endpoints['resources'] = base+'/projects/{}/resources/'
+    endpoints['s3'] = base+'/projects/{}/s3/'
+
     return endpoints
 
 def get_auth_header(conf):
