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": "[email protected]",
"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)
azure-functions
azure-identity
azure-keyvault-secrets
opencensus-ext-azure
Also, add all the environment variables in function app by navigating to Settings ->Environment Variables -> App settings .
I am able to deploy the code successfully to function app.