diff --git a/Dockerfile b/Dockerfile.cicd similarity index 69% rename from Dockerfile rename to Dockerfile.cicd index ef94870..002f584 100644 --- a/Dockerfile +++ b/Dockerfile.cicd @@ -1,4 +1,4 @@ FROM python:3.11-slim WORKDIR app -COPY ./requirements.txt ./ +COPY requirements-cicd.txt requirements.txt RUN pip install --upgrade pip && pip install -r requirements.txt \ No newline at end of file diff --git a/Dockerfile.dashboard b/Dockerfile.dashboard new file mode 100644 index 0000000..e81768c --- /dev/null +++ b/Dockerfile.dashboard @@ -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"] \ No newline at end of file diff --git a/dashboard/main.py b/dashboard/main.py new file mode 100644 index 0000000..c2b7f10 --- /dev/null +++ b/dashboard/main.py @@ -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) \ No newline at end of file diff --git a/dashboard/settings.py b/dashboard/settings.py new file mode 100644 index 0000000..9207fed --- /dev/null +++ b/dashboard/settings.py @@ -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) \ No newline at end of file diff --git a/dashboard/sidebar.py b/dashboard/sidebar.py new file mode 100644 index 0000000..f161455 --- /dev/null +++ b/dashboard/sidebar.py @@ -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() diff --git a/requirements.txt b/requirements-cicd.txt similarity index 100% rename from requirements.txt rename to requirements-cicd.txt diff --git a/requirements-dashboard.txt b/requirements-dashboard.txt new file mode 100644 index 0000000..9a1c4fa --- /dev/null +++ b/requirements-dashboard.txt @@ -0,0 +1,3 @@ +GitPython==3.1.32 +streamlit==1.25.0 +streamlit_js_eval==0.1.5 \ No newline at end of file