Skip to content

Commit 417d737

Browse files
DEVOPS-263 application to get image details
1 parent 2fb6de6 commit 417d737

File tree

7 files changed

+349
-2
lines changed

7 files changed

+349
-2
lines changed

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ ipython_config.py
9999
# This is especially recommended for binary packages to ensure reproducibility, and is more
100100
# commonly ignored for libraries.
101101
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102-
#poetry.lock
102+
poetry.lock
103103

104104
# pdm
105105
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
@@ -159,4 +159,4 @@ cython_debug/
159159
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
160160
# and can be added to the global gitignore or merged into this file. For a more nuclear
161161
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
162-
#.idea/
162+
.idea/

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,34 @@
11
# azure-app-to-get-image-details-from-acr
22
This app helps in displaying image details available in an azure container registry
3+
4+
# Why the requirement of a application to list azure acr image lists
5+
6+
* Every Cloud platforms will have their own Role based access control system.
7+
8+
* Sometimes cross collaboration between teams will be happening and they will not be having any access to see resources
9+
that belongs to other teams.
10+
11+
* As an example there is a platform engineering team that maintains docker imagges which are widely used and frequeently built,
12+
and if teams wont have access to the registry they won't be able to list out the differnt versions of images available.
13+
14+
➦ This application can resolve above mentioned problem.
15+
16+
# What is needed
17+
18+
* The user need to input the name of the Azure container registry he needs to list images from.
19+
20+
* The Application will get the image names and available tags displayed in the UI.
21+
22+
# Pre requisites
23+
24+
* `azure-identity python sdk` is used for authentiation with azure. Make sure the service principal you are using
25+
has enough permissions to list images, Acess ACR's. This class `EnvironmentCredential` been used.
26+
[azure-identity](https://learn.microsoft.com/en-us/python/api/overview/azure/identity-readme?view=azure-python)
27+
28+
* Microsoft Azure Container Registry Client Library for Python is used to list all images and tags in a ACR.
29+
[azure-containerregistry](https://learn.microsoft.com/en-us/python/api/overview/azure/containerregistry-readme?view=azure-python)
30+
31+
32+
33+
34+

acr_images.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import os
2+
import sys
3+
import argparse
4+
import logging
5+
from dotenv import load_dotenv
6+
from azure.identity import DefaultAzureCredential, EnvironmentCredential
7+
from azure.containerregistry import ContainerRegistryClient
8+
from date_time import date_time
9+
from azure_resource_graph_query import validate_input_acr_name
10+
from azure.core.exceptions import HttpResponseError, ClientAuthenticationError
11+
12+
13+
def return_acr_endpoint(acr_name:str):
14+
"""
15+
Function returns acr url from its name
16+
:param acr_name:
17+
:return:
18+
"""
19+
acr_endpoint = f'https://{acr_name.lower()}.azurecr.io'
20+
return acr_endpoint
21+
22+
23+
def list_image_tag_details(acr_name: str):
24+
"""
25+
list the images and total tag counts per images
26+
:param acr_name:
27+
:return:
28+
"""
29+
credential = EnvironmentCredential()
30+
31+
repositories = get_image_names_from_acr(acr_name=acr_name)
32+
acr_url = return_acr_endpoint(acr_name=acr_name)
33+
print(f'Image details will be fetched from : {acr_url}')
34+
# validate acr input
35+
valid = validate_input_acr_name(acr_name=acr_name)
36+
37+
# Image - tag count
38+
image_tag_count = {}
39+
client = ContainerRegistryClient(endpoint=acr_url, credential=credential)
40+
for repository in repositories:
41+
tags_list = []
42+
# print(f'image name is: {acr_url}/{repository}')
43+
# print(f'available tags are:')
44+
for tag in client.list_tag_properties(repository = repository):
45+
tag_details = {}
46+
# print(f'Image name : {repository}')
47+
# print(f'Tag name : {tag.name}')
48+
tag_details['tag_name'] = tag.name
49+
created_on = tag.created_on
50+
fmt_date = date_time(created_on)
51+
tag_details['tag_created_on'] = fmt_date
52+
# print(f'Tag created on {fmt_date}')
53+
tag_sha = tag.digest
54+
tag_details['tag_sha'] = tag_sha
55+
tags_list.append(tag_details)
56+
# print(f'{"*" * 50}')
57+
image_tag_count[repository] = tags_list
58+
59+
return image_tag_count # dictionary with key as image name and value as available tag and details
60+
61+
62+
def get_image_names_from_acr(acr_name: str):
63+
"""
64+
this will be used to pull image details from acr
65+
:param acr_name:
66+
:return:
67+
"""
68+
try:
69+
acr_url = return_acr_endpoint(acr_name=acr_name)
70+
71+
# define credentials
72+
credential= EnvironmentCredential()
73+
client = ContainerRegistryClient(endpoint= acr_url, credential= credential)
74+
75+
repository_list = []
76+
# List all images in acr
77+
repository_names = client.list_repository_names()
78+
# print(repository_names)
79+
for repository in repository_names:
80+
# print(repository)
81+
repository_list.append(repository)
82+
83+
return repository_list
84+
85+
except ClientAuthenticationError:
86+
logging.error("Authentication failed. Please check your credentials.")
87+
return {"error": "Authentication failed. Please check your credentials."}
88+
89+
except HttpResponseError as e:
90+
logging.error(f"HTTP error occurred: {e.response.status_code} {e.response.reason}")
91+
return {"error": f"HTTP error occurred: {e.response.status_code} {e.response.reason}"}
92+
93+
except Exception as e:
94+
logging.error(f"An unexpected error occurred: {str(e)}")
95+
return {"error": f"An unexpected error occurred: {str(e)}"}
96+
97+
98+
def main():
99+
"""
100+
to run main function
101+
:return:
102+
"""
103+
parser = argparse.ArgumentParser("To fetch image details from Azure container registry")
104+
parser.add_argument("--acr_name", help="Azure container registry name", type=str, required=True)
105+
106+
args = parser.parse_args()
107+
108+
acr_name = args.acr_name
109+
load_dotenv()
110+
# return_acr_endpoint(acr_name=acr_name)
111+
# get_image_names_from_acr(acr_name=acr_name)
112+
image_tag_count = list_image_tag_details(acr_name=acr_name)
113+
# print(image_tag_count)
114+
115+
if __name__ == "__main__":
116+
main()

azure_resource_graph_query.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from azure.identity import DefaultAzureCredential, EnvironmentCredential
2+
import azure.mgmt.resourcegraph as arg
3+
import logging
4+
import os
5+
import sys
6+
from dotenv import load_dotenv
7+
import streamlit as st
8+
9+
def run_azure_rg_query():
10+
"""
11+
Run a resource graph query to get the acr details
12+
:return: subscription_id str
13+
"""
14+
credential = EnvironmentCredential()
15+
# Create Azure Resource Graph client and set options
16+
arg_client = arg.ResourceGraphClient(credential)
17+
18+
query = f"""
19+
resources
20+
| where type == "microsoft.containerregistry/registries"
21+
| project name
22+
"""
23+
24+
print(f"query is {query}")
25+
26+
# Create query
27+
arg_query = arg.models.QueryRequest(query=query)
28+
29+
# Run query
30+
arg_result = arg_client.resources(arg_query)
31+
32+
# Show Python object
33+
acr_details_from_query = arg_result.data
34+
return acr_details_from_query
35+
36+
def validate_input_acr_name(acr_name: str):
37+
"""
38+
validate the input acr name
39+
:param acr_name:
40+
:return:
41+
"""
42+
acr_details_from_query= run_azure_rg_query()
43+
# Convert all elements to lowercase
44+
acr_details_from_query_list = [item['name'].lower() for item in acr_details_from_query]
45+
46+
acr = acr_name.lower()
47+
48+
if acr in acr_details_from_query_list:
49+
print(f"{acr} is valid.")
50+
else:
51+
print(f"ACR {acr} can not be found in azure")
52+
st.text(f"ACR {acr} can not be found in azure 🚫")
53+
sys.exit(f"Error: ACR {acr} can not be found in azure")
54+
return False
55+
56+
57+
def main():
58+
"""
59+
To test the script
60+
:return:
61+
"""
62+
load_dotenv()
63+
logging.info("ARG query being prepared......")
64+
run_azure_rg_query(subscription_name="TECH-ARCHITECTS-NONPROD")
65+
logging.info("ARG query Completed......")
66+
67+
68+
if __name__ == "__main__":
69+
main()

date_time.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import datetime
2+
3+
def date_time(date_time: datetime):
4+
"""
5+
Returns formated date time from datetime class
6+
:param date_time:
7+
:return:
8+
"""
9+
datetime_obj = date_time
10+
# Extract year, month, day
11+
year = datetime_obj.year
12+
month = datetime_obj.month
13+
day = datetime_obj.day
14+
hour = datetime_obj.hour
15+
minute = datetime_obj.minute
16+
17+
# Get the full name of the weekday
18+
weekday_name = datetime_obj.strftime('%A')
19+
# print(f"Year: {year}")
20+
# print(f"Month: {month}")
21+
# print(f"Day: {day}")
22+
# print(f"Weekday (name): {weekday_name}")
23+
# print(f'Hour {hour}')
24+
# print(f'Minute {minute}')
25+
formatted_date = f'{year}-{month}-{day} {weekday_name} {hour}:{minute}'
26+
return formatted_date
27+
28+
def main():
29+
""" to run the script"""
30+
day = datetime.datetime.now()
31+
date_time(day)
32+
33+
if __name__ == "__main__":
34+
main()

pyproject.toml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[tool.poetry]
2+
name = "azure-app-to-get-image-details-from-acr"
3+
version = "0.1.0"
4+
description = ""
5+
authors = ["githubofkrishnadhas <[email protected]>"]
6+
readme = "README.md"
7+
8+
[tool.poetry.dependencies]
9+
python = "^3.11"
10+
azure-identity = "^1.17.1"
11+
azure-containerregistry = "^1.2.0"
12+
python-dotenv = "^1.0.1"
13+
requests = "^2.32.3"
14+
argparse = "^1.4.0"
15+
streamlit = "^1.36.0"
16+
azure-mgmt-resource = "^23.1.1"
17+
azure-mgmt-resourcegraph = "^8.0.0"
18+
19+
20+
[build-system]
21+
requires = ["poetry-core"]
22+
build-backend = "poetry.core.masonry.api"

streamlit.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import streamlit as st
2+
import os
3+
import json
4+
from acr_images import *
5+
from dotenv import load_dotenv
6+
7+
def get_data_from_acr(acr_name: str):
8+
"""
9+
Image details from azure container registry
10+
:param acr_name:
11+
:return:
12+
"""
13+
image_details = list_image_tag_details(acr_name=acr_name)
14+
with st.container():
15+
st.write(f'Total number of images available in "{acr_name}" ACR - :hash:{len(image_details)}')
16+
st.write(f'The image details are as follows: :arrow_heading_down:')
17+
18+
# Convert the dictionary to a JSON string
19+
json_string = json.dumps(image_details, indent=4)
20+
# print(json_string)
21+
st.json(json_string)
22+
23+
24+
st.write(f'Thanks for visiting :crossed_fingers:')
25+
# Add footer at the end of the page
26+
st.markdown("""
27+
<style>
28+
.footer {
29+
position: fixed;
30+
bottom: 0;
31+
left: 0;
32+
width: 100%;
33+
text-align: center;
34+
background-color: #000000; /* Set background color to black */
35+
padding: 10px;
36+
font-size: 14px;
37+
color: white; /* Set text color to white */
38+
}
39+
.footer a {
40+
color: white; /* Link color */
41+
text-decoration: none; /* Remove underline from link */
42+
font-weight: bold; /* Make text bold */
43+
}
44+
</style>
45+
<div class="footer">
46+
This is built & maintained by <a href="mailto:[email protected],[email protected]"><strong>githubofkrishnadhas</strong></a>
47+
</div>
48+
""", unsafe_allow_html=True)
49+
50+
51+
def main():
52+
"""
53+
run the code
54+
:return:
55+
"""
56+
load_dotenv()
57+
st.header("Azure Container Registry Image Details",divider='rainbow')
58+
59+
acr_name = st.text_input("Enter Azure Container Registry Name:")
60+
61+
if st.button("Submit"):
62+
st.session_state.acr_name = acr_name
63+
st.rerun()
64+
65+
# Check if acr_name is stored in session state
66+
if 'acr_name' in st.session_state:
67+
print(f'Received input as {st.session_state.acr_name}')
68+
st.write(f"You have provided the input : {st.session_state.acr_name}")
69+
get_data_from_acr(acr_name=st.session_state.acr_name)
70+
71+
72+
73+
if __name__ == "__main__":
74+
main()

0 commit comments

Comments
 (0)