i had a function app that had a python script running in it i was deploying it from vs code and it was working fine , now i created a new resource and a new function and i redid the same configuration , now i tried to deploy the same folder to that function app and everything is in the app files but the function is not recognized as a function is in the function app i have all the necessary files in the directory so what can the issue be ? import os import logging import requests import azure.functions as func from azure.identity import DefaultAzureCredential from azure.keyvault.secrets import SecretClient from opencensus.ext.azure.log_exporter import AzureLogHandler # Global configuration EMAIL = os.getenv("EMAIL") KEY_VAULT_NAME = os.getenv("KEY_VAULT_NAME") SECRET_NAME = os.getenv("SECRET_NAME") ACTIVE_INSTANCE = os.getenv("ACTIVE_INSTANCE") LOGIC_APP_URL = os.getenv("LOGIC_APP_URL") # Instance-specific configuration BASE_URL = os.getenv("BASE_URL") WORKSPACE_ID = os.getenv("WORKSPACE_ID") OBJECT_TYPE_ID = os.getenv("OBJECT_TYPE_ID") PROJECT_ID_ATTR = os.getenv("PROJECT_ID_ATTR") PROJECT_KEY_ATTR = os.getenv("PROJECT_KEY_ATTR") PROJECT_NAME_ATTR = os.getenv("PROJECT_NAME_ATTR") PROJECT_DESCRIPTION_ATTR = os.getenv("PROJECT_DESCRIPTION_ATTR") PROJECT_CATEGORY_ATTR = os.getenv("PROJECT_CATEGORY_ATTR") PROJECT_TYPEKEY_ATTR = os.getenv("PROJECT_TYPEKEY_ATTR") PROJECT_LEAD_ATTR = os.getenv("PROJECT_LEAD_ATTR") PROJECT_LEADID_ATTR = os.getenv("PROJECT_LEADID_ATTR") PROJECT_SIMPLIFIED_ATTR = os.getenv("PROJECT_SIMPLIFIED_ATTR") PROJECT_STYLED_ATTR = os.getenv("PROJECT_STYLED_ATTR") PROJECT_ISPRIVATE_ATTR = os.getenv("PROJECT_ISPRIVATE_ATTR") PROJECT_TOTALISSUECOUNT_ATTR = os.getenv("PROJECT_TOTALISSUECOUNT_ATTR") PROJECT_LASTISSUEUPDATE_ATTR = os.getenv("PROJECT_LASTISSUEUPDATE_ATTR") PROJECT_ARCHIVEDDATE_ATTR = os.getenv("PROJECT_ARCHIVEDDATE_ATTR") # Setup logging logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) logger.addHandler(AzureLogHandler(connection_string='InstrumentationKey=42d4e759-407c-4774-8869-ef7343513438')) def get_secret_from_key_vault(key_vault_name, secret_name): """Retrieve a secret from Azure Key Vault.""" kv_uri = f"https://{key_vault_name}.vault.azure.net" credential = DefaultAzureCredential() client = SecretClient(vault_url=kv_uri, credential=credential) secret = client.get_secret(secret_name) return secret.value def send_error_report(error_message): """Send an error report via Logic App.""" payload = { "To": "example@email.com", "CC": "", "Response": "No", "Subject": "Jira Authentication Error", "Body": f"An error occurred during Jira authentication: {error_message}" } try: response = requests.post(LOGIC_APP_URL, json=payload) response.raise_for_status() logger.info("Error report sent successfully.") except requests.exceptions.RequestException as e: logger.error(f"Failed to send error report: {e}") def authenticate_jira(base_url, email, api_token): """Authenticate with the Jira Cloud instance.""" session = requests.Session() session.auth = (email, api_token) session.headers.update({'Content-Type': 'application/json'}) try: response = session.get(f"{base_url}/rest/api/3/myself") response.raise_for_status() logger.info("Authentication successful.") except requests.exceptions.RequestException as e: logger.error(f"Failed to authenticate: {e}") #send_error_report(str(e)) # Call the Logic App to send an error report raise e return session def get_jira_projects(session): """Fetch all Jira projects using the paginated API.""" logger.info("Fetching Jira projects") url = f"{BASE_URL}/rest/api/3/project/search" params = {"expand": "description,lead,issueTypes,url,projectKeys,permissions,insight"} projects = [] while url: try: response = session.get(url, params=params) if response.status_code != 200: logger.error(f"Error fetching Jira projects: {response.status_code} {response.text}") response.raise_for_status() data = response.json() projects.extend(data.get("values", [])) url = data.get("nextPage") # Continue pagination except requests.exceptions.RequestException as e: logger.error(f"Error fetching Jira projects: {e}") break logger.info(f"Fetched {len(projects)} projects") for project in projects: logger.info(f"Fetched project: {project.get('id')} - {project.get('key')} - {project.get('name')}") return projects def search_asset_object(session, project_id): """Search for an asset object using AQL.""" url = f"https://api.atlassian.com/jsm/assets/workspace/{WORKSPACE_ID}/v1/object/aql" query = { "qlQuery": f'objectType = "Jira Project" AND "Project ID" = "{project_id}"' } try: response = session.post(url, json=query) response.raise_for_status() return response.json().get("values", []) except requests.exceptions.RequestException as e: logger.error(f"Error searching asset object for project {project_id}: {e}") return [] def create_asset_object(session, project_data): """Create a new asset object.""" url = f"https://api.atlassian.com/jsm/assets/workspace/{WORKSPACE_ID}/v1/object/create" payload = format_payload(project_data) try: response = session.post(url, json=payload) response.raise_for_status() logger.info(f"Asset created for project {project_data['id']} ({project_data['key']})") except requests.exceptions.RequestException as e: logger.error(f"Error creating asset object for project {project_data['id']}: {e}") def update_asset_object(session, asset_id, project_data): """Update an existing asset object.""" url = f"https://api.atlassian.com/jsm/assets/workspace/{WORKSPACE_ID}/v1/object/{asset_id}" payload = format_payload(project_data) try: response = session.put(url, json=payload) response.raise_for_status() logger.info(f"Asset updated for project {project_data['id']} ({project_data['key']})") except requests.exceptions.RequestException as e: logger.error(f"Error updating asset object {asset_id} for project {project_data['id']}: {e}") def format_payload(project): """Format payload for asset creation or update based on configuration.""" last_issue_update_time = project.get("insight", {}).get("lastIssueUpdateTime") formatted_last_issue_update_time = None if last_issue_update_time: formatted_last_issue_update_time = last_issue_update_time.split("T")[0] description_long = project.get("description") shortened_description = None if description_long: shortened_description = description_long[:255] return { "objectTypeId": OBJECT_TYPE_ID, "attributes": [ {"objectTypeAttributeId": PROJECT_ID_ATTR, "objectAttributeValues": [{"value": project.get("id")}]}, {"objectTypeAttributeId": PROJECT_KEY_ATTR, "objectAttributeValues": [{"value": project.get("key")}]}, {"objectTypeAttributeId": PROJECT_NAME_ATTR, "objectAttributeValues": [{"value": project.get("name")}]}, {"objectTypeAttributeId": PROJECT_DESCRIPTION_ATTR, "objectAttributeValues": [{"value": shortened_description}]}, {"objectTypeAttributeId": PROJECT_CATEGORY_ATTR, "objectAttributeValues": [{"value": project.get("projectCategory", {}).get("name")}]}, {"objectTypeAttributeId": PROJECT_TYPEKEY_ATTR, "objectAttributeValues": [{"value": project.get("projectTypeKey")}]}, {"objectTypeAttributeId": PROJECT_LEAD_ATTR, "objectAttributeValues": [{"value": project.get("lead", {}).get("accountId")}]}, {"objectTypeAttributeId": PROJECT_LEADID_ATTR, "objectAttributeValues": [{"value": project.get("lead", {}).get("accountId")}]}, {"objectTypeAttributeId": PROJECT_SIMPLIFIED_ATTR, "objectAttributeValues": [{"value": project.get("simplified")}]}, {"objectTypeAttributeId": PROJECT_STYLED_ATTR, "objectAttributeValues": [{"value": project.get("style")}]}, {"objectTypeAttributeId": PROJECT_ISPRIVATE_ATTR, "objectAttributeValues": [{"value": project.get("isPrivate")}]}, {"objectTypeAttributeId": PROJECT_TOTALISSUECOUNT_ATTR, "objectAttributeValues": [{"value": project.get("insight", {}).get("totalIssueCount")}]}, {"objectTypeAttributeId": PROJECT_LASTISSUEUPDATE_ATTR, "objectAttributeValues": [{"value": formatted_last_issue_update_time}]}, {"objectTypeAttributeId": PROJECT_ARCHIVEDDATE_ATTR, "objectAttributeValues": [{"value": project.get("archivedDate")}]} ] } def sync_jira_main(): """Main logic to process Jira projects and sync with assets.""" logger.info("Starting Jira Projects to Asset synchronization") try: # Retrieve the API token from Key Vault API_TOKEN = get_secret_from_key_vault(KEY_VAULT_NAME, SECRET_NAME) except Exception as e: message = f"Error fetching API token: {e}" logger.error(message) raise Exception(message) try: # Authenticate and create a session session = authenticate_jira(BASE_URL, EMAIL, API_TOKEN) except Exception as e: send_error_report(str(e)) message = f"Error fetching Jira projects: {e}" logger.error(message) raise Exception(message) projects = get_jira_projects(session) for project in projects: project_id = project.get("id") if not project_id: logger.warning("Project without ID encountered, skipping.") continue existing_assets = search_asset_object(session, project_id) if len(existing_assets) == 0: create_asset_object(session, project) elif len(existing_assets) > 1: logger.error(f"Multiple asset objects found for project {project_id} ({project.get('key')}).") else: update_asset_object(session, existing_assets[0].get("id"), project) logger.info("Jira Projects to Asset synchronization completed.") app = func.FunctionApp(http_auth_level=func.AuthLevel.FUNCTION) @app.route(route="leanjira_script", methods=["GET", "POST"]) def leansyncjira_script(req: func.HttpRequest) -> func.HttpResponse: logging.info('Python HTTP trigger function processed a request.') # Call the sync_jira main function sync_jira_main() return func.HttpResponse("Synchronization completed.", status_code=200) Continue reading...