Add version management dashboard
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
FROM python:3.11-slim
|
FROM python:3.11-slim
|
||||||
WORKDIR app
|
WORKDIR app
|
||||||
COPY ./requirements.txt ./
|
COPY requirements-cicd.txt requirements.txt
|
||||||
RUN pip install --upgrade pip && pip install -r requirements.txt
|
RUN pip install --upgrade pip && pip install -r requirements.txt
|
||||||
25
Dockerfile.dashboard
Normal file
25
Dockerfile.dashboard
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
From python:3.11-slim
|
||||||
|
|
||||||
|
WORKDIR app
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get -y install --no-install-recommends curl vim git openssh-client \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY requirements-dashboard.txt requirements.txt
|
||||||
|
RUN pip install --no-cache-dir --upgrade pip && pip install -r requirements.txt
|
||||||
|
|
||||||
|
ARG SSH_PRIVATE_KEY
|
||||||
|
ARG GITHUB_TOKEN
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
ENV PYTHONIOENCODING=UTF-8
|
||||||
|
ENV GITHUB_TOKEN=${GITHUB_TOKEN}
|
||||||
|
|
||||||
|
RUN mkdir /root/.ssh \
|
||||||
|
&& echo "$SSH_PRIVATE_KEY" >> /root/.ssh/id_rsa \
|
||||||
|
&& chmod 600 /root/.ssh/id_rsa \
|
||||||
|
&& ssh-keyscan github.com >> /root/.ssh/known_hosts
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
ENTRYPOINT ["streamlit", "run", "dashboard/main.py"]
|
||||||
48
dashboard/main.py
Normal file
48
dashboard/main.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import pandas as pd
|
||||||
|
import streamlit as st
|
||||||
|
from streamlit_js_eval import streamlit_js_eval
|
||||||
|
from settings import *
|
||||||
|
from sidebar import show_sidebar
|
||||||
|
|
||||||
|
def highlight_disabled_col(value):
|
||||||
|
return 'background-color: #F0F2F6'
|
||||||
|
|
||||||
|
def init_page():
|
||||||
|
st.set_page_config(
|
||||||
|
page_title='DataSaker Version Management',
|
||||||
|
layout='wide'
|
||||||
|
)
|
||||||
|
st.header('DataSaker Version Management')
|
||||||
|
st.subheader(get_datasaker())
|
||||||
|
|
||||||
|
if __name__=='__main__':
|
||||||
|
|
||||||
|
init_page()
|
||||||
|
|
||||||
|
col1, col2 = st.columns([7, 3])
|
||||||
|
with col1:
|
||||||
|
st.subheader('Service')
|
||||||
|
|
||||||
|
if st.button('Data Reload'):
|
||||||
|
git_pull()
|
||||||
|
streamlit_js_eval(js_expressions='parent.window.location.reload()')
|
||||||
|
|
||||||
|
df = pd.DataFrame.from_dict(get_service())
|
||||||
|
regex = '^release-[0-9]+.[0-9]+.[0-9]+$'
|
||||||
|
edited_df = st.data_editor(
|
||||||
|
df.style.applymap(highlight_disabled_col),
|
||||||
|
key='data_editor',
|
||||||
|
column_config={
|
||||||
|
'type': st.column_config.TextColumn('Type', disabled=True),
|
||||||
|
'name': st.column_config.TextColumn('Name', disabled=True, width='medium'),
|
||||||
|
'latest_candidate_version': st.column_config.TextColumn('Candidate Latest Version', disabled=True),
|
||||||
|
'candidate_version': st.column_config.TextColumn('Candidate Version', validate=regex),
|
||||||
|
'release_version': st.column_config.TextColumn('Release Version', validate=regex),
|
||||||
|
'product_version': st.column_config.TextColumn('Product Version', validate=regex)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
with col2:
|
||||||
|
st.text('Edited Rows')
|
||||||
|
st.write(st.session_state['data_editor']['edited_rows'])
|
||||||
|
|
||||||
|
show_sidebar(df, edited_df)
|
||||||
57
dashboard/settings.py
Normal file
57
dashboard/settings.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import json, requests, os
|
||||||
|
from git import Repo
|
||||||
|
|
||||||
|
repo = Repo('.')
|
||||||
|
file_path = './version.json'
|
||||||
|
repo.config_writer().set_value('user', 'name', 'dsk-minchulahn').release()
|
||||||
|
repo.config_writer().set_value('user', 'email', 'minchulahn@ex-em.com').release()
|
||||||
|
|
||||||
|
def get_datasaker():
|
||||||
|
return json.load(open(file_path, 'r'))['datasaker']
|
||||||
|
|
||||||
|
def get_service():
|
||||||
|
return json.load(open(file_path, 'r'))['service']
|
||||||
|
|
||||||
|
def get_commit_id():
|
||||||
|
return repo.head.commit
|
||||||
|
|
||||||
|
def get_tags():
|
||||||
|
return repo.tags
|
||||||
|
|
||||||
|
def diff():
|
||||||
|
if len(repo.index.diff(repo.head.commit)) > 0:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def diff_remote_head():
|
||||||
|
repo.remote().fetch()
|
||||||
|
remote_head = repo.remote().refs['main'].commit
|
||||||
|
|
||||||
|
if repo.head.commit == remote_head:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def git_pull():
|
||||||
|
repo.remote().fetch()
|
||||||
|
repo.remotes.origin.pull()
|
||||||
|
|
||||||
|
def git_push(commit_message, extended_description):
|
||||||
|
repo.git.add('version.json')
|
||||||
|
|
||||||
|
if len(repo.index.diff(repo.head.commit)) > 0:
|
||||||
|
repo.index.commit(f'{commit_message}\n\n{extended_description}')
|
||||||
|
repo.git.push(force=False)
|
||||||
|
|
||||||
|
def publish_release(selected_tag, release_title, release_describe):
|
||||||
|
api_url = 'https://api.github.com/repos/cloudmoa/sample-app/releases'
|
||||||
|
github_token = os.environ.get('GITHUB_TOKEN')
|
||||||
|
headers = {'Authorization': f'Bearer {github_token}', 'Accept': 'application/vnd.github.v3+json'}
|
||||||
|
release_data = {
|
||||||
|
'tag_name': selected_tag,
|
||||||
|
'name': release_title,
|
||||||
|
'body': release_describe,
|
||||||
|
'draft': False,
|
||||||
|
'prerelease': False
|
||||||
|
}
|
||||||
|
return requests.post(api_url, json=release_data, headers=headers)
|
||||||
78
dashboard/sidebar.py
Normal file
78
dashboard/sidebar.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import json, time
|
||||||
|
import streamlit as st
|
||||||
|
from streamlit_js_eval import streamlit_js_eval
|
||||||
|
from settings import *
|
||||||
|
|
||||||
|
def set_extended_description(edited_df):
|
||||||
|
json_edited_df = json.loads(edited_df.to_json(orient='records'))
|
||||||
|
extended_description_value = []
|
||||||
|
for idx, changed_idx in enumerate(st.session_state['data_editor']['edited_rows']):
|
||||||
|
if idx == 0:
|
||||||
|
extended_description_value.append(json_edited_df[changed_idx]['name'])
|
||||||
|
else:
|
||||||
|
extended_description_value.append('\n' + json_edited_df[changed_idx]['name'])
|
||||||
|
|
||||||
|
if 'candidate_version' in st.session_state['data_editor']['edited_rows'][changed_idx]:
|
||||||
|
extended_description_value.append(f"candidate: {st.session_state['data_editor']['edited_rows'][changed_idx]['candidate_version']}")
|
||||||
|
if 'release_version' in st.session_state['data_editor']['edited_rows'][changed_idx]:
|
||||||
|
extended_description_value.append(f"release: {st.session_state['data_editor']['edited_rows'][changed_idx]['release_version']}")
|
||||||
|
if 'product_version' in st.session_state['data_editor']['edited_rows'][changed_idx]:
|
||||||
|
extended_description_value.append(f"product: {st.session_state['data_editor']['edited_rows'][changed_idx]['product_version']}")
|
||||||
|
return '\n'.join(extended_description_value)
|
||||||
|
|
||||||
|
def set_version_json(edited_df):
|
||||||
|
data = {'datasaker': get_datasaker(),'service': json.loads(edited_df.to_json(orient='records'))}
|
||||||
|
with open('version.json', 'w') as file:
|
||||||
|
json.dump(data, file, indent=4)
|
||||||
|
|
||||||
|
def show_sidebar(df, edited_df):
|
||||||
|
with st.sidebar:
|
||||||
|
st.subheader('Git Push')
|
||||||
|
with st.expander('Git Push', expanded=True):
|
||||||
|
commit_message = st.text_input('Commit Message', value='Update version.json')
|
||||||
|
extended_description = st.text_area('Extended description', value=set_extended_description(edited_df), height=200)
|
||||||
|
|
||||||
|
if st.button(key='push', label='Commit changes'):
|
||||||
|
if commit_message:
|
||||||
|
if diff_remote_head():
|
||||||
|
if df.equals(edited_df):
|
||||||
|
st.warning('No changes have been made', icon='⚠️')
|
||||||
|
else:
|
||||||
|
set_version_json(edited_df)
|
||||||
|
git_push(commit_message, extended_description)
|
||||||
|
st.success('Success', icon='✅')
|
||||||
|
time.sleep(1)
|
||||||
|
streamlit_js_eval(js_expressions='parent.window.location.reload()')
|
||||||
|
else:
|
||||||
|
st.error('Updates were rejected because the tip of your current branch is behind', icon='🚨')
|
||||||
|
|
||||||
|
st.divider()
|
||||||
|
|
||||||
|
st.subheader('Draft a new release')
|
||||||
|
with st.expander('Draft a new release'):
|
||||||
|
tags = [tag.name for tag in get_tags()]
|
||||||
|
tags.insert(0, "")
|
||||||
|
new_tag = st.text_input('Create a new tag')
|
||||||
|
|
||||||
|
if new_tag:
|
||||||
|
if new_tag in tags:
|
||||||
|
st.warning('Existing tag', icon='⚠️')
|
||||||
|
else:
|
||||||
|
tags.insert(0, new_tag)
|
||||||
|
|
||||||
|
selected_tag = st.selectbox('Choose a tag', tags)
|
||||||
|
release_title = st.text_input('Release title', value=selected_tag)
|
||||||
|
release_describe = st.text_area('Describe this release', value='## Production에 변경된 Version')
|
||||||
|
|
||||||
|
if st.button(key='release', label='Publish release'):
|
||||||
|
response = publish_release(selected_tag, release_title, release_describe)
|
||||||
|
|
||||||
|
if response.status_code == 201:
|
||||||
|
st.success('Release created successfully', icon='✅')
|
||||||
|
git_pull()
|
||||||
|
streamlit_js_eval(js_expressions='parent.window.location.reload()')
|
||||||
|
else:
|
||||||
|
st.error(f'Failed to create release. Status code: {response.status_code}', icon='🚨')
|
||||||
|
st.error(f'Response: {response.text}', icon='🚨')
|
||||||
|
|
||||||
|
st.divider()
|
||||||
3
requirements-dashboard.txt
Normal file
3
requirements-dashboard.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
GitPython==3.1.32
|
||||||
|
streamlit==1.25.0
|
||||||
|
streamlit_js_eval==0.1.5
|
||||||
Reference in New Issue
Block a user