diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs deleted file mode 100644 index 7be9334..0000000 --- a/.git-blame-ignore-revs +++ /dev/null @@ -1 +0,0 @@ -7ea4664db24c272e25511919cf093b79d2aa5ea7 diff --git a/.gitignore b/.gitignore index 746c8b0..91e7dfc 100644 --- a/.gitignore +++ b/.gitignore @@ -164,4 +164,6 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +.idea/ +.terraform/ +tfplan \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b1c6e5f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "git-blame.gitWebUrl": "" +} diff --git a/cv_images/.gitkeep b/cv_images/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/cv_output/.gitkeep b/cv_output/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/cv_pages/.gitkeep b/cv_pages/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/cvbia/.vscode/settings.json b/cvbia/.vscode/settings.json new file mode 100644 index 0000000..2997cfd --- /dev/null +++ b/cvbia/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "git-blame.gitWebUrl": "" +} \ No newline at end of file diff --git a/cvbia/Dockerfile.slackbot b/cvbia/Dockerfile.slackbot new file mode 100644 index 0000000..eaef0ce --- /dev/null +++ b/cvbia/Dockerfile.slackbot @@ -0,0 +1,20 @@ +FROM --platform=linux/amd64 python:3.10 + +WORKDIR /src + +COPY requirements.txt /src + +RUN pip install --upgrade -r requirements.txt + +COPY src ./ + +EXPOSE 8000 + +HEALTHCHECK --interval=30s --timeout=30s \ + CMD curl -f http://localhost:8000/health/ || exit 1 + +# todo: files need to be deleted (migrate to temp files) +# RUN groupadd -r api && useradd --no-log-init -r -g api api +# USER api + +CMD ["python", "slack_bot.py"] diff --git a/cvbia/Dockerfile.streamlit b/cvbia/Dockerfile.streamlit new file mode 100644 index 0000000..feb9576 --- /dev/null +++ b/cvbia/Dockerfile.streamlit @@ -0,0 +1,16 @@ +FROM --platform=linux/amd64 python:3.10 + +WORKDIR /src + +COPY requirements.txt /src +RUN pip install --upgrade -r requirements.txt + +COPY src ./ + +RUN mkdir ~/.streamlit +RUN cp config.toml ~/.streamlit/config.toml + +EXPOSE 80 + +ENTRYPOINT ["streamlit", "run"] +CMD ["streamlit_app.py"] diff --git a/cvbia/Makefile b/cvbia/Makefile new file mode 100644 index 0000000..19feef3 --- /dev/null +++ b/cvbia/Makefile @@ -0,0 +1,22 @@ +docker/cvbia/login: + az acr login -n cvbia + +apps/cvbia_streamlit/build: + docker build -f Dockerfile.streamlit -t cvbia_streamlit:latest . + +apps/cvbia_streamlit/push: + docker tag cvbia:latest cvbia.azurecr.io/cvbia_streamlit:latest + docker push cvbia.azurecr.io/cvbia_streamlit:latest + +apps/cvbia_streamlit/run: + docker run -p 80:80 cvbia:latest + +apps/cvbia_slackbot/build: + docker build -f Dockerfile.slackbot -t cvbia_slackbot:latest . + +apps/cvbia_slackbot/push: + docker tag cvbia_slackbot:latest cvbia.azurecr.io/cvbia_slackbot:latest + docker push cvbia.azurecr.io/cvbia_slackbot:latest + +apps/cvbia_slackbot/run: + docker run -p 80:80 cvbia_slackbot:latest diff --git a/requirements.txt b/cvbia/requirements.txt similarity index 66% rename from requirements.txt rename to cvbia/requirements.txt index 282baf9..815fa61 100644 --- a/requirements.txt +++ b/cvbia/requirements.txt @@ -1,10 +1,11 @@ -black +dropbox>=11.36.0,<=12.0.0 +pypdf +python-pptx +PyMuPDF>=1.22.0 pyyaml reportlab<4.0.0 +slack_bolt streamlit streamlit-cropper -pre-commit -pypdf -PyMuPDF>=1.22.0 -python-pptx tqdm +uvicorn diff --git a/backgrounds/background1.jpg b/cvbia/src/backgrounds/background1.jpg similarity index 100% rename from backgrounds/background1.jpg rename to cvbia/src/backgrounds/background1.jpg diff --git a/backgrounds/background2.jpg b/cvbia/src/backgrounds/background2.jpg similarity index 100% rename from backgrounds/background2.jpg rename to cvbia/src/backgrounds/background2.jpg diff --git a/cvbia/src/clients/dropbox_client.py b/cvbia/src/clients/dropbox_client.py new file mode 100644 index 0000000..849aab5 --- /dev/null +++ b/cvbia/src/clients/dropbox_client.py @@ -0,0 +1,36 @@ +import os + +import dropbox +import yaml + +BASE_PATH = "/home/Team/testing cvbia" +DROPBOX_REFRESH_TOKEN = os.environ["DROPBOX_REFRESH_TOKEN"] +DROPBOX_APP_KEY = os.environ["DROPBOX_APP_KEY"] + +def convert_email_to_file_name(email): + email = email.split("@")[0].replace(".", "_").lower() + return f"{BASE_PATH}/{email}" + + +class TransferData: + def upload_file_yaml(self, content, file_to): + """upload a file to Dropbox using API v2""" + with dropbox.Dropbox(oauth2_refresh_token=DROPBOX_REFRESH_TOKEN, app_key=DROPBOX_APP_KEY) as dbx: + dbx.files_upload( + bytes(content, "utf-8"), file_to, mode=dropbox.files.WriteMode.overwrite + ) + + def upload_file(self, file_from, file_to): + """upload a file to Dropbox using API v2""" + with dropbox.Dropbox(oauth2_refresh_token=DROPBOX_REFRESH_TOKEN, app_key=DROPBOX_APP_KEY) as dbx: + with open(file_from, "rb") as f: + dbx.files_upload( + f.read(), file_to, mode=dropbox.files.WriteMode.overwrite + ) + + def download_file(self, email): + with dropbox.Dropbox(oauth2_refresh_token=DROPBOX_REFRESH_TOKEN, app_key=DROPBOX_APP_KEY) as dbx: + file_path = convert_email_to_file_name(email=email) + _, f = dbx.files_download(f"{file_path}.yaml") + yaml_content = yaml.safe_load(f.content) + return yaml_content diff --git a/cvbia/src/config.toml b/cvbia/src/config.toml new file mode 100644 index 0000000..366b8f3 --- /dev/null +++ b/cvbia/src/config.toml @@ -0,0 +1,137 @@ +# Below are all the sections and options you can have in ~/.streamlit/config.toml. + +[global] + +# By default, Streamlit checks if the Python watchdog module is available and, if not, prints a warning asking for you to install it. The watchdog module is not required, but highly recommended. It improves Streamlit's ability to detect changes to files in your filesystem. +# If you'd like to turn off this warning, set this to True. +# Default: false +disableWatchdogWarning = false + +# Configure the ability to share apps to the cloud. +# Should be set to one of these values: - "off" : turn off sharing. - "s3" : share to S3, based on the settings under the [s3] section of this config file. +# Default: "off" +sharingMode = "off" + +# If True, will show a warning when you run a Streamlit-enabled script via "python my_script.py". +# Default: true +showWarningOnDirectExecution = true + +# Level of logging: 'error', 'warning', 'info', or 'debug'. +# Default: 'info' +# logLevel = "debug" + + +[client] + +# Whether to enable st.cache. +# Default: true +caching = true + +# If false, makes your Streamlit script not draw to a Streamlit app. +# Default: true +displayEnabled = true + + +[runner] + +# Allows you to type a variable or string by itself in a single line of Python code to write it to the app. +# Default: true +magicEnabled = true + +# Install a Python tracer to allow you to stop or pause your script at any point and introspect it. As a side-effect, this slows down your script's execution. +# Default: false +installTracer = false + +# Sets the MPLBACKEND environment variable to Agg inside Streamlit to prevent Python crashing. +# Default: true +fixMatplotlib = true + + +[server] + +# List of folders that should not be watched for changes. Relative paths will be taken as relative to the current working directory. +# Example: ['/home/user1/env', 'relative/path/to/folder'] +# Default: [] +folderWatchBlacklist = [''] + + + +# If false, will attempt to open a browser window on start. +# Default: false unless (1) we are on a Linux box where DISPLAY is unset, or (2) server.liveSave is set. +headless = true + +# Immediately share the app in such a way that enables live monitoring, and post-run analysis. +# Default: false +liveSave = false + +# Automatically rerun script when the file is modified on disk. +# Default: false +runOnSave = false + +# The port where the server will listen for client and browser connections. +# Default: 8501 +port = 80 + +# Enables support for Cross-Origin Request Sharing, for added security. +# Default: true +enableCORS = false + + +[browser] + +# Internet address of the server server that the browser should connect to. Can be IP address or DNS name. +# Default: 'localhost' +serverAddress = "0.0.0.0" + +# Whether to send usage statistics to Streamlit. +# Default: true +gatherUsageStats = true + +# Port that the browser should use to connect to the server when in liveSave mode. +# Default: whatever value is set in server.port. +serverPort = 80 + +[s3] + +# Name of the AWS S3 bucket to save apps. +# Default: (unset) +# bucket = + +# URL root for external view of Streamlit apps. +# Default: (unset) +# url = + +# Access key to write to the S3 bucket. +# Leave unset if you want to use an AWS profile. +# Default: (unset) +# accessKeyId = + +# Secret access key to write to the S3 bucket. +# Leave unset if you want to use an AWS profile. +# Default: (unset) +#secretAccessKey = + +# Make the shared app visible only to users who have been granted view permission. If you are interested in this option, contact us at support@streamlit.io. +# Default: false +# requireLoginToView = false + +# The "subdirectory" within the S3 bucket where to save apps. +# S3 calls paths "keys" which is why the keyPrefix is like a subdirectory. Use "" to mean the root directory. +# Default: "" +keyPrefix = "" + +# AWS region where the bucket is located, e.g. "us-west-2". +# Default: (unset) +# region = + +# AWS credentials profile to use. +# Leave unset to use your default profile. +# Default: (unset) +# profile = + + +[logger] + +# Level of logging: 'error', 'warning', 'info', or 'debug'. +# Default: 'info' +level = "debug" diff --git a/cv_data.yaml b/cvbia/src/cv_data.yaml similarity index 100% rename from cv_data.yaml rename to cvbia/src/cv_data.yaml diff --git a/cvbia/src/cv_output/cv.pdf b/cvbia/src/cv_output/cv.pdf new file mode 100644 index 0000000..256d9f3 Binary files /dev/null and b/cvbia/src/cv_output/cv.pdf differ diff --git a/cvbia/src/cv_output/cv.pptx b/cvbia/src/cv_output/cv.pptx new file mode 100644 index 0000000..32278fb Binary files /dev/null and b/cvbia/src/cv_output/cv.pptx differ diff --git a/cvbia/src/cv_pages/cv1.pdf b/cvbia/src/cv_pages/cv1.pdf new file mode 100644 index 0000000..1d92fb3 Binary files /dev/null and b/cvbia/src/cv_pages/cv1.pdf differ diff --git a/cvbia/src/cv_pages/cv2.pdf b/cvbia/src/cv_pages/cv2.pdf new file mode 100644 index 0000000..e174034 Binary files /dev/null and b/cvbia/src/cv_pages/cv2.pdf differ diff --git a/cvbia/src/cv_pages/cv3.pdf b/cvbia/src/cv_pages/cv3.pdf new file mode 100644 index 0000000..09d9be9 Binary files /dev/null and b/cvbia/src/cv_pages/cv3.pdf differ diff --git a/fonts/ProximaNova-Regular.ttf b/cvbia/src/fonts/ProximaNova-Regular.ttf similarity index 100% rename from fonts/ProximaNova-Regular.ttf rename to cvbia/src/fonts/ProximaNova-Regular.ttf diff --git a/fonts/ProximaNova-Semibold.ttf b/cvbia/src/fonts/ProximaNova-Semibold.ttf similarity index 100% rename from fonts/ProximaNova-Semibold.ttf rename to cvbia/src/fonts/ProximaNova-Semibold.ttf diff --git a/cvbia/src/helpers/draw.py b/cvbia/src/helpers/draw.py new file mode 100644 index 0000000..99d1487 --- /dev/null +++ b/cvbia/src/helpers/draw.py @@ -0,0 +1,126 @@ +import io +import itertools +import textwrap + +from PIL import Image, ImageDraw +from reportlab.lib.pagesizes import A4 +from reportlab.lib.utils import ImageReader +from reportlab.pdfbase.pdfmetrics import stringWidth + + +def write( + c: object, + x: int, + y: int, + text: str, + width: int = 60, + font: str = "regular", + punto: int = 12, + color: str = "dark_grey", + spacing: int = 12, + url: str = None, +): + """ + Writes given text input with specified arguments. + + Arguments: + c: canvas object + x: x coordinate on the page + y: y coordinate on the page + text: text input to be written, must be a string + font: regular, bold + punto: font size + color: white, trans_white, purple, dark_grey, light_grey + spacing: line spacing, only used for mutliple line inputs + url: URL to be written, starts with the URL domain, i.e. no "https://www." + """ + # SET FONT + c.setFont(font, punto) + + # SET COLOR + if color == "white": + c.setFillColorRGB(0.95, 0.95, 0.95, 1) + elif color == "trans_white": + c.setFillColorRGB(0.95, 0.95, 0.95, 0.8) + elif color == "purple": + c.setFillColorRGB(108 / 255, 29 / 255, 96 / 255, 1) + elif color == "dark_grey": + c.setFillColorRGB(0.5, 0.5, 0.5, 1) + elif color == "light_grey": + c.setFillColorRGB(0.3, 0.3, 0.3, 1) + else: + raise Exception( + f"{color} is not available. Try: white, trans_white, purple, dark_grey, light_grey" + ) + + # DRAW STRING: for loop is used to manage multiple line inputs + if text: + for line in list( + itertools.chain.from_iterable( + [textwrap.wrap(x, width=width) for x in text.splitlines()] + ) + ): + c.drawString(x, y, f"{line}") + if url is not None: + c.linkURL( + "https://www." + url, + rect=( + x, + y, + x + (0.66 * stringWidth(text, font, punto + 4)), + y + punto, + ), + relative=0, + thickness=0, + ) + y -= spacing + + return y + + +def draw_background(c, image=None, path="backgrounds/background1.jpg"): + """ + Fills the background of document. + """ + if image: + bg_image = image + c.drawImage(bg_image, 0, 0, width=A4[1], height=A4[0]) + elif path: + bg_image = path + c.drawImage(bg_image, 0, 0, width=A4[1], height=A4[0]) + + return None + + +def draw_picture(c, image=None, path: str = "images/tom_cruise.jpg"): + """ + Crops and draws the circular photo of the person on CV. + """ + if image: + im = Image.open(io.BytesIO(image)) + bigsize = (im.size[0] * 3, im.size[1] * 3) + mask = Image.new("L", bigsize, 0) + draw = ImageDraw.Draw(mask) + draw.ellipse((0, 0) + bigsize, fill=255) + mask = mask.resize(im.size, Image.LANCZOS) + im.putalpha(mask) + img_byte_arr = io.BytesIO() + im.save(img_byte_arr, format="PNG") + img_byte_arr = img_byte_arr.getvalue() + pp_image = ImageReader(io.BytesIO(img_byte_arr)) + c.drawImage(pp_image, 20, 455, width=100, height=100, mask="auto") + elif path: + im = Image.open(path) + bigsize = (im.size[0] * 3, im.size[1] * 3) + mask = Image.new("L", bigsize, 0) + draw = ImageDraw.Draw(mask) + draw.ellipse((0, 0) + bigsize, fill=255) + mask = mask.resize(im.size, Image.LANCZOS) + im.putalpha(mask) + img_byte_arr = io.BytesIO() + im.save(img_byte_arr, format="PNG") + img_byte_arr = img_byte_arr.getvalue() + pp_image = ImageReader(io.BytesIO(img_byte_arr)) + c.drawImage(pp_image, 20, 455, width=100, height=100, mask="auto") + + return None diff --git a/cvbia/src/helpers/dropbox.py b/cvbia/src/helpers/dropbox.py new file mode 100644 index 0000000..fc87d66 --- /dev/null +++ b/cvbia/src/helpers/dropbox.py @@ -0,0 +1,19 @@ +import yaml + +from clients.dropbox_client import TransferData, convert_email_to_file_name + + +def load_data_to_dropbox(yaml_text, file): + transferData = TransferData() + cv_data = yaml.safe_load(yaml_text) + base_file_name = convert_email_to_file_name(cv_data["email"]) + + transferData.upload_file(file, file_to=f"{base_file_name}.pdf") + transferData.upload_file_yaml(yaml_text, file_to=f"{base_file_name}.yaml") + + +def dowload_file_from_dropbox(yaml_text): + transferData = TransferData() + cv_data = yaml.safe_load(yaml_text) + email = cv_data["email"] + transferData.download_file(email=email) diff --git a/functions.py b/cvbia/src/helpers/file_helper.py similarity index 69% rename from functions.py rename to cvbia/src/helpers/file_helper.py index e28322b..4c72c67 100644 --- a/functions.py +++ b/cvbia/src/helpers/file_helper.py @@ -1,175 +1,15 @@ -from reportlab.lib.pagesizes import A4 -from reportlab.pdfbase.pdfmetrics import stringWidth -from reportlab.lib.utils import ImageReader -from PIL import Image, ImageDraw -import fitz import io -import itertools import os +import sys from pathlib import Path + +import fitz from pptx import Presentation from pptx.util import Cm from pypdf import PdfMerger -import sys -import textwrap from tqdm import trange -def cleanup_files(directories: list = ["cv_pages", "cv_images"]): - for directory in directories: - for filename in os.listdir(directory): - f = os.path.join(directory, filename) - # checking if it is a file - if os.path.isfile(f) and filename != ".gitkeep": - os.remove(f) - - -def write( - c: object, - x: int, - y: int, - text: str, - width: int = 60, - font: str = "regular", - punto: int = 12, - color: str = "dark_grey", - spacing: int = 12, - url: str = None, -): - """ - Writes given text input with specified arguments. - - Arguments: - c: canvas object - x: x coordinate on the page - y: y coordinate on the page - text: text input to be written, must be a string - font: regular, bold - punto: font size - color: white, trans_white, purple, dark_grey, light_grey - spacing: line spacing, only used for mutliple line inputs - url: URL to be written, starts with the URL domain, i.e. no "https://www." - """ - # SET FONT - c.setFont(font, punto) - - # SET COLOR - if color == "white": - c.setFillColorRGB(0.95, 0.95, 0.95, 1) - elif color == "trans_white": - c.setFillColorRGB(0.95, 0.95, 0.95, 0.8) - elif color == "purple": - c.setFillColorRGB(108 / 255, 29 / 255, 96 / 255, 1) - elif color == "dark_grey": - c.setFillColorRGB(0.5, 0.5, 0.5, 1) - elif color == "light_grey": - c.setFillColorRGB(0.3, 0.3, 0.3, 1) - else: - raise Exception( - f"{color} is not available. Try: white, trans_white, purple, dark_grey, light_grey" - ) - - # DRAW STRING: for loop is used to manage multiple line inputs - if text: - for line in list( - itertools.chain.from_iterable( - [textwrap.wrap(x, width=width) for x in text.splitlines()] - ) - ): - c.drawString(x, y, f"{line}") - if url is not None: - c.linkURL( - "https://www." + url, - rect=( - x, - y, - x + (0.66 * stringWidth(text, font, punto + 4)), - y + punto, - ), - relative=0, - thickness=0, - ) - y -= spacing - - return y - - -def draw_background(c, image=None, path="backgrounds/background1.jpg"): - """ - Fills the background of document. - """ - if image: - bg_image = image - c.drawImage(bg_image, 0, 0, width=A4[1], height=A4[0]) - elif path: - bg_image = path - c.drawImage(bg_image, 0, 0, width=A4[1], height=A4[0]) - - return None - - -def draw_picture(c, image=None, path: str = "images/tom_cruise.jpg"): - """ - Crops and draws the circular photo of the person on CV. - """ - if image: - im = Image.open(io.BytesIO(image)) - bigsize = (im.size[0] * 3, im.size[1] * 3) - mask = Image.new("L", bigsize, 0) - draw = ImageDraw.Draw(mask) - draw.ellipse((0, 0) + bigsize, fill=255) - mask = mask.resize(im.size, Image.LANCZOS) - im.putalpha(mask) - img_byte_arr = io.BytesIO() - im.save(img_byte_arr, format="PNG") - img_byte_arr = img_byte_arr.getvalue() - pp_image = ImageReader(io.BytesIO(img_byte_arr)) - c.drawImage(pp_image, 20, 455, width=100, height=100, mask="auto") - elif path: - im = Image.open(path) - bigsize = (im.size[0] * 3, im.size[1] * 3) - mask = Image.new("L", bigsize, 0) - draw = ImageDraw.Draw(mask) - draw.ellipse((0, 0) + bigsize, fill=255) - mask = mask.resize(im.size, Image.LANCZOS) - im.putalpha(mask) - img_byte_arr = io.BytesIO() - im.save(img_byte_arr, format="PNG") - img_byte_arr = img_byte_arr.getvalue() - pp_image = ImageReader(io.BytesIO(img_byte_arr)) - c.drawImage(pp_image, 20, 455, width=100, height=100, mask="auto") - - return None - - -def page_end_checker(y: int, exp, spacing: int = 12, punto: int = 10): - """ - This function is used to assess if the current cv page will be enough - to display the next experience block. If not, a new page should be created. - """ - - def size_checker(y: int, text): - if text: - for line in text.splitlines(): - if len(text.splitlines()) > 1: - y -= spacing - return y - - y = size_checker(y, text=f"{exp['title']} @ {exp['company']}") - y -= 10 - y = size_checker(y, text=f"{exp['start']} - {exp['end']}") - y -= 20 - y = size_checker(y, text=exp["description"]) - y -= 3 - y = size_checker(y, text=exp["technologies"]) - y -= 25 - print(y) - if y < 0: - return True - else: - return False - - def merge_pdfs(input_directory: str = "cv_pages", output_directory: str = "cv_output"): """ This function merges all pdfs into one, in the given directory. @@ -188,7 +28,6 @@ def merge_pdfs(input_directory: str = "cv_pages", output_directory: str = "cv_ou pdfs.append(f) pdfs = sorted(pdfs, reverse=False) - print(pdfs) merger = PdfMerger() @@ -368,3 +207,47 @@ def yaml_checker(yaml): ), "'technologies' field is missing in one of 'experience' fields. you can leave them empty but you shouldn't delete the fields." return None + + +def update_yaml_by_key(current_yaml, key, data): + if type(current_yaml[key]) == str: + current_yaml[key] = data + else: + current_yaml[key].append(data) + return current_yaml + + +def page_end_checker(y: int, exp, spacing: int = 12, punto: int = 10): + """ + This function is used to assess if the current cv page will be enough + to display the next experience block. If not, a new page should be created. + """ + + def size_checker(y: int, text): + if text: + for line in text.splitlines(): + if len(text.splitlines()) > 1: + y -= spacing + return y + + y = size_checker(y, text=f"{exp['title']} @ {exp['company']}") + y -= 10 + y = size_checker(y, text=f"{exp['start']} - {exp['end']}") + y -= 20 + y = size_checker(y, text=exp["description"]) + y -= 3 + y = size_checker(y, text=exp["technologies"]) + y -= 25 + if y < 0: + return True + else: + return False + + +def cleanup_files(directories: list = ["cv_pages", "cv_images"]): + for directory in directories: + for filename in os.listdir(directory): + f = os.path.join(directory, filename) + # checking if it is a file + if os.path.isfile(f) and filename != ".gitkeep": + os.remove(f) diff --git a/generate_cv.py b/cvbia/src/helpers/generate_cv.py similarity index 98% rename from generate_cv.py rename to cvbia/src/helpers/generate_cv.py index 9d9079a..0fae942 100644 --- a/generate_cv.py +++ b/cvbia/src/helpers/generate_cv.py @@ -1,9 +1,16 @@ -from functions import * +import yaml from reportlab.lib.pagesizes import landscape from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont from reportlab.pdfgen import canvas -import yaml + +from helpers.draw import A4, draw_background, draw_picture, write +from helpers.file_helper import ( + cleanup_files, + merge_pdfs, + page_end_checker, + yaml_checker, +) def generate_cv(yaml_input: str = None, image=None): diff --git a/images/repo_image.png b/cvbia/src/images/repo_image.png similarity index 100% rename from images/repo_image.png rename to cvbia/src/images/repo_image.png diff --git a/photos/tom_cruise.jpg b/cvbia/src/photos/tom_cruise.jpg similarity index 100% rename from photos/tom_cruise.jpg rename to cvbia/src/photos/tom_cruise.jpg diff --git a/cvbia/src/slack_bot.py b/cvbia/src/slack_bot.py new file mode 100644 index 0000000..043931e --- /dev/null +++ b/cvbia/src/slack_bot.py @@ -0,0 +1,556 @@ +import os + +from slack_bolt import App +from slack_bolt.adapter.socket_mode import SocketModeHandler + +from clients.dropbox_client import TransferData +from helpers.dropbox import load_data_to_dropbox +from helpers.file_helper import update_yaml_by_key +from helpers.generate_cv import generate_cv + + +app = App(token=os.environ["SLACK_BOT_TOKEN"]) + + +def update_cv_in_dropbox(key, content, email): + yaml_content = TransferData().download_file(email) + new_yaml_content = update_yaml_by_key(current_yaml=yaml_content, key=key, data=content) + generate_cv(str(new_yaml_content)) + load_data_to_dropbox(str(new_yaml_content), "cv_output/cv.pdf") + +@app.view("view_experience") +def update_experience(ack, body, client): + ack() + + state_values = body["view"]["state"]["values"] + title = state_values["title_id_modal_input"]["title_id_value"]["value"] + description = state_values["description_id_modal_input"]["description_id_value"][ + "value" + ] + company = state_values["company_id_modal_input"]["company_id_value"]["value"] + tech = state_values["tech_id_modal_input"]["tech_id_value"]["value"] + start_date = state_values["start_date_id_modal_input"]["start_date_id_value"][ + "value" + ] + end_date = state_values["end_date_id_modal_input"]["end_date_id_value"]["value"] + + yaml_to_update = { + "title": title, + "company": company, + "start": start_date, + "end": end_date, + "description": description, + "technologies": tech, + } + + user = body["user"]["id"] + user_email = app.client.users_info(user=user)["user"]["profile"]["email"] + + update_cv_in_dropbox(key="experience", content=yaml_to_update, email=user_email) + client.chat_postMessage( + channel=user, text="Your experience had been updated in your CV." + ) + + +@app.view("view_education") +def update_education(ack, body, client): + ack() + state_values = body["view"]["state"]["values"] + + degree = state_values["degree_id_modal_input"]["degree_id_value"]["value"] + institution = state_values["institution_id_modal_input"]["institution_id_value"][ + "value" + ] + year = state_values["year_id_modal_input"]["year_id_value"]["value"] + + yaml_to_update = {"degree": degree, "institution": institution, "year": year} + user_email = app.client.users_info(user=body["user"]["id"])["user"]["profile"][ + "email" + ] + user = body["user"]["id"] + + update_cv_in_dropbox(key="education", content=yaml_to_update, email=user_email) + client.chat_postMessage( + channel=user, text="Your education had been updated in your CV." + ) + + +@app.view("view_roles") +def update_roles(ack, body, client): + ack() + state_values = body["view"]["state"]["values"] + title = state_values["title_id_modal_input"]["title_id_value"]["value"] + description = state_values["description_id_modal_input"]["description_id_value"][ + "value" + ] + + yaml_to_update = {"title": title, "description": description} + + user = body["user"]["id"] + user_email = app.client.users_info(user=user)["user"]["profile"]["email"] + + update_cv_in_dropbox(key="roles", content=yaml_to_update, email=user_email) + client.chat_postMessage( + channel=user, text=f"Your roles had been updated in your CV." + ) + + +@app.view("view_certification") +def update_certification(ack, body, client): + ack() + title = body["view"]["state"]["values"]["title_id_modal_input"]["title_id_value"][ + "value" + ] + user = body["user"]["id"] + user_email = app.client.users_info(user=user)["user"]["profile"]["email"] + + yaml_to_update = { + "title": title, + } + update_cv_in_dropbox(key="certifications", content=yaml_to_update, email=user_email) + client.chat_postMessage( + channel=user, text="Your certification had been updated in your CV." + ) + + +@app.view("view_competences") +def update_competences(ack, body, client): + ack() + state_values = body["view"]["state"]["values"] + title = state_values["title_id_modal_input"]["title_id_value"]["value"] + description = state_values["description_id_modal_input"]["description_id_value"][ + "value" + ] + yaml_to_update = {"title": title, "description": description} + + user = body["user"]["id"] + user_email = app.client.users_info(user=user)["user"]["profile"]["email"] + + update_cv_in_dropbox(key="competences", content=yaml_to_update, email=user_email) + client.chat_postMessage( + channel=user, text="Your competences had been updated in your CV." + ) + + +@app.action("experience") +def open_modal_experience(ack, client, body): + ack() + + client.views_open( + trigger_id=body["trigger_id"], + # View payload + view={ + "type": "modal", + # View identifier + "callback_id": "view_experience", + "title": {"type": "plain_text", "text": "CVBIA"}, + "blocks": [ + { + "type": "section", + "block_id": "say_hello_msg", + "text": { + "type": "mrkdwn", + "text": "Hello! You will add an experience in your CV.", + "verbatim": False, + }, + }, + { + "type": "input", + "block_id": "title_id_modal_input", + "label": { + "type": "plain_text", + "text": "Title :bam:", + "emoji": True, + }, + "element": { + "type": "plain_text_input", + "action_id": "title_id_value", + "placeholder": { + "type": "plain_text", + "text": "Analytics Engineer", + "emoji": False, + }, + }, + }, + { + "type": "input", + "block_id": "description_id_modal_input", + "label": { + "type": "plain_text", + "text": "Description :squirrel:", + "emoji": True, + }, + "element": { + "type": "plain_text_input", + "action_id": "description_id_value", + "placeholder": { + "type": "plain_text", + "text": "Helping to achieve Fabulousness.", + "emoji": False, + }, + }, + }, + { + "type": "input", + "block_id": "company_id_modal_input", + "label": { + "type": "plain_text", + "text": "Company name :rage1:", + "emoji": True, + }, + "element": { + "type": "plain_text_input", + "action_id": "company_id_value", + "placeholder": { + "type": "plain_text", + "text": "Pollos hermanos", + "emoji": False, + }, + }, + }, + { + "type": "input", + "block_id": "tech_id_modal_input", + "label": { + "type": "plain_text", + "text": "Technologies :crycat:", + "emoji": True, + }, + "element": { + "type": "plain_text_input", + "action_id": "tech_id_value", + "placeholder": { + "type": "plain_text", + "text": "Python, Scala", + "emoji": False, + }, + }, + }, + { + "type": "input", + "block_id": "start_date_id_modal_input", + "label": { + "type": "plain_text", + "text": "Start date :trump-smile:", + "emoji": True, + }, + "element": { + "type": "plain_text_input", + "action_id": "start_date_id_value", + "placeholder": { + "type": "plain_text", + "text": "2018 March", + "emoji": False, + }, + }, + }, + { + "type": "input", + "block_id": "end_date_id_modal_input", + "label": { + "type": "plain_text", + "text": "End date :side_eye:", + "emoji": True, + }, + "element": { + "type": "plain_text_input", + "action_id": "end_date_id_value", + "placeholder": { + "type": "plain_text", + "text": "2019 March", + "emoji": False, + }, + }, + }, + ], + "clear_on_close": True, # when modal closes, fiels are clear + "notify_on_close": False, + "close": {"type": "plain_text", "text": "Cancel", "emoji": True}, + "submit": { + "type": "plain_text", + "text": "Submit :shutuptakemoney:", + "emoji": True, + }, + }, + ) + + +@app.action("education") +def open_modal_education(ack, client, body): + ack() + + client.views_open( + trigger_id=body["trigger_id"], + # View payload + view={ + "type": "modal", + # View identifier + "callback_id": "view_education", + "title": {"type": "plain_text", "text": "CVBIA"}, + "blocks": [ + { + "type": "section", + "block_id": "say_hello_msg", + "text": { + "type": "mrkdwn", + "text": "Hello! You will add a new CV entry in education.", + "verbatim": False, + }, + }, + { + "type": "input", + "block_id": "degree_id_modal_input", + "label": { + "type": "plain_text", + "text": "Degree :bam:", + "emoji": True, + }, + "element": { + "type": "plain_text_input", + "action_id": "degree_id_value", + "placeholder": { + "type": "plain_text", + "text": "MSc in Acting (Cum Laude)", + "emoji": False, + }, + }, + }, + { + "type": "input", + "block_id": "institution_id_modal_input", + "label": { + "type": "plain_text", + "text": "Institution :squirrel:", + "emoji": True, + }, + "element": { + "type": "plain_text_input", + "action_id": "institution_id_value", + "placeholder": { + "type": "plain_text", + "text": "Glen Ridge High School.", + "emoji": False, + }, + }, + }, + { + "type": "input", + "block_id": "year_id_modal_input", + "label": { + "type": "plain_text", + "text": "Period (years) :rage1:", + "emoji": True, + }, + "element": { + "type": "plain_text_input", + "action_id": "year_id_value", + "placeholder": { + "type": "plain_text", + "text": "2012-2018", + "emoji": False, + }, + }, + }, + ], + "clear_on_close": True, # when modal closes, fiels are clear + "notify_on_close": False, + "close": {"type": "plain_text", "text": "Cancel", "emoji": True}, + "submit": { + "type": "plain_text", + "text": "Submit :shutuptakemoney:", + "emoji": True, + }, + }, + ) + + +@app.action("certification") +def open_modal_certification(ack, client, body): + ack() + + client.views_open( + trigger_id=body["trigger_id"], + # View payload + view={ + "type": "modal", + # View identifier + "callback_id": f"view_certification", + "title": {"type": "plain_text", "text": "CVBIA"}, + "blocks": [ + { + "type": "section", + "block_id": "say_hello_msg", + "text": { + "type": "mrkdwn", + "text": f"Hello! You will add a new CV entry in your certifications.", + "verbatim": False, + }, + }, + { + "type": "input", + "block_id": "title_id_modal_input", + "label": { + "type": "plain_text", + "text": "Title :bam:", + "emoji": True, + }, + "element": { + "type": "plain_text_input", + "action_id": "title_id_value", + "placeholder": { + "type": "plain_text", + "text": "Analytics Engineer", + "emoji": False, + }, + }, + }, + ], + "clear_on_close": True, # when modal closes, fiels are clear + "notify_on_close": False, + "close": {"type": "plain_text", "text": "Cancel", "emoji": True}, + "submit": { + "type": "plain_text", + "text": "Submit :shutuptakemoney:", + "emoji": True, + }, + }, + ) + + +@app.action("roles") +@app.action("competences") +def open_modal_general(ack, client, body, action): + ack() + + key_to_update = action["value"] + client.views_open( + trigger_id=body["trigger_id"], + # View payload + view={ + "type": "modal", + # View identifier + "callback_id": f"view_{key_to_update}", + "title": {"type": "plain_text", "text": "CVBIA"}, + "blocks": [ + { + "type": "section", + "block_id": "say_hello_msg", + "text": { + "type": "mrkdwn", + "text": f"Hello! You will add a new CV entry in your {key_to_update}.", + "verbatim": False, + }, + }, + { + "type": "input", + "block_id": "title_id_modal_input", + "label": { + "type": "plain_text", + "text": "Title :bam:", + "emoji": True, + }, + "element": { + "type": "plain_text_input", + "action_id": "title_id_value", + "placeholder": { + "type": "plain_text", + "text": "Analytics Engineer", + "emoji": False, + }, + }, + }, + { + "type": "input", + "block_id": "description_id_modal_input", + "label": { + "type": "plain_text", + "text": "Description :bam:", + "emoji": True, + }, + "element": { + "type": "plain_text_input", + "action_id": "description_id_value", + "placeholder": { + "type": "plain_text", + "text": "Description", + "emoji": False, + }, + }, + }, + ], + "clear_on_close": True, # when modal closes, fiels are clear + "notify_on_close": False, + "close": {"type": "plain_text", "text": "Cancel", "emoji": True}, + "submit": { + "type": "plain_text", + "text": "Submit :shutuptakemoney:", + "emoji": True, + }, + }, + ) + + +@app.command("/update_cv") +def open_modal(ack, say): + ack() + + blocks = [ + { + "type": "actions", + "elements": [ + { + "type": "button", + "value": "education", + "text": {"type": "plain_text", "text": "education", "emoji": True}, + "style": "primary", + "action_id": "education", + }, + { + "type": "button", + "value": "roles", + "text": {"type": "plain_text", "text": "roles", "emoji": True}, + "style": "primary", + "action_id": "roles", + }, + { + "type": "button", + "value": "competences", + "text": { + "type": "plain_text", + "text": "competences", + "emoji": True, + }, + "style": "primary", + "action_id": "competences", + }, + { + "type": "button", + "value": "certification", + "text": { + "type": "plain_text", + "text": "certifications", + "emoji": True, + }, + "style": "primary", + "action_id": "certification", + }, + { + "type": "button", + "value": "experience", + "text": { + "type": "plain_text", + "text": "work experience", + "emoji": True, + }, + "style": "primary", + "action_id": "experience", + }, + ], + } + ] + say(text="Hello! Which field of your CV would you like to update? :fiesta_parrot:") + say(blocks=blocks) + + +if __name__ == "__main__": + SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start() diff --git a/streamlit_app.py b/cvbia/src/streamlit_app.py similarity index 76% rename from streamlit_app.py rename to cvbia/src/streamlit_app.py index 2aa06dc..94b4ec0 100644 --- a/streamlit_app.py +++ b/cvbia/src/streamlit_app.py @@ -1,13 +1,18 @@ -from functions import * -from generate_cv import * -import streamlit as st -from streamlit_cropper import st_cropper +import io import os + import fitz +import streamlit as st +from streamlit_cropper import st_cropper + +from helpers.draw import Image +from helpers.dropbox import dowload_file_from_dropbox, load_data_to_dropbox +from helpers.generate_cv import generate_cv ## PAGE TITLE st.title("CVBIA") + ## IMAGE UPLOAD WIDGET with st.container(): uploaded_image = st.file_uploader("Upload and crop your picture: (png, jpg, jpeg)") @@ -42,6 +47,28 @@ # st.download_button("Download PDF", file, file_name=f"{cv_data['first_name']}_{cv_data['first_name']}_cv.pdf") st.sidebar.download_button("Download PPTX", file, file_name="cv.pptx") + +def button_action_load_data_to_dropbox(yaml_text, file_name): + try: + load_data_to_dropbox(yaml_text, file_name) + except KeyError: + st.warning("Please set env var DROPBOX_TOKEN", icon="⚠️") + + +st.sidebar.button( + "Load CV to Xebia Dropbox YAML", + key="CV", + on_click=button_action_load_data_to_dropbox, + args=(yaml_text, "cv_output/cv.pdf"), +) + +st.sidebar.button( + "Download CV", + key="CVDownload", + on_click=dowload_file_from_dropbox, + args=(yaml_text,), +) + ## PDF PREVIEW: need to convert resulting cv.pdf to images, in order to display cv preview on streamlit page dpi = 100 zoom = dpi / 72 diff --git a/dev-requirements.txt b/dev-requirements.txt new file mode 100644 index 0000000..31a5bd8 --- /dev/null +++ b/dev-requirements.txt @@ -0,0 +1,2 @@ +black +pre-commit \ No newline at end of file diff --git a/terraform/.terraform.lock.hcl b/terraform/.terraform.lock.hcl new file mode 100644 index 0000000..5a05c46 --- /dev/null +++ b/terraform/.terraform.lock.hcl @@ -0,0 +1,41 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/azuread" { + version = "2.41.0" + hashes = [ + "h1:aF2XhKoCNoxxdeZXy8Y6uYfSS+iRT78DmfMCO5b12No=", + "zh:1c3e89cf19118fc07d7b04257251fc9897e722c16e0a0df7b07fcd261f8c12e7", + "zh:32032e1539da9c986adea46a4fc702b8e3dea287ac825fdbc57ce459e6cfd25b", + "zh:37841384780140f7026faf55700087d5e08f504168d67ae7f0d28e7132430c82", + "zh:4c272ac7bf8b1aa913eeeca5fcdff25af432c6ae397d963710004b546a068936", + "zh:8febf43db3f0bd9d46b2ee31eecf8f3ba912a4553ae049657e0f0c68bdff90a0", + "zh:b60a3e92cd2a7af916d70d94d01c67d5bfabf68963f1b5ff75d8a310d66a4522", + "zh:b9a8b9554f8ba9d306427aa8b2c0894242286972a61ab33300d6d8ac57bf93ab", + "zh:ce7a79964a68a6086fa97e1f38d1c015d24d0732dd7876e179704c383a79f9aa", + "zh:e305876a4d44739135264bb11be2c5489903682bb89f7b25561d38be31a9087d", + "zh:ec3f63a848b3b2521cda0cf7650c4f85b50cc8905e5065f775d7c855833ab306", + "zh:ef2a1ba97f15db88510bbc9b1af611c9bc8bee660d4d8b3b826424e1025487aa", + "zh:f40db067d868567e199e16054a554aba5d89c3a19a4264c813dc7212724eeeea", + ] +} + +provider "registry.terraform.io/hashicorp/azurerm" { + version = "3.61.0" + constraints = ">= 3.0.0" + hashes = [ + "h1:ygpAv2ggEm3aMJTPvlvVaPdegoxriKpDSXSbIO9qqB4=", + "zh:1441e4e9b63801bc2432b1c06e0d202e66946c57abbf553d7f88fba5986e8f59", + "zh:291f0db2808724119a079f9a30254dbeae4a450dbacdd8bbb821d9332b842b25", + "zh:3069754dece944a9a5b2b5394e09bc393a22211951a73a1fdccc5c9fa41785d2", + "zh:4c6502f93da330d4b91ff2fbf198700619566386fd92c17ad14492b78a135403", + "zh:5f0fe6ca017b30534362e2ffcab03bcad9f08b59485948ba751248ee4b7deb7d", + "zh:76cdce8bb5fc73b65a21fb7eae943096397c53acda30fbe5da2892c92dabd3f9", + "zh:8f990887703da526d71ba25dcc259797e763397f23c3c04003c0706893db474c", + "zh:90854ec0570367b0d0c29e69f7426aca6dd85e6a3f8e3ed108ab5570311e587c", + "zh:a9450a8e1e45a577a62f252b383fe6a7159ee782a16ed8142377264b86e47fa4", + "zh:cc228b766a089be615e5d8b88342670a6a4cd05d44eb535232b8613c8d23218b", + "zh:d817088f245dce195c25492f6b2c8c26f02fd863cb5da5b689733b7c6bba3559", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} diff --git a/terraform/Makefile b/terraform/Makefile new file mode 100644 index 0000000..4732b2f --- /dev/null +++ b/terraform/Makefile @@ -0,0 +1,14 @@ +init: + terraform init + +init/upgrade: + terraform init -upgrade + +init/reconfigure: + terraform init -reconfigure + +plan: + terraform plan -out tfplan + +apply: + terraform apply tfplan diff --git a/terraform/locals.tf b/terraform/locals.tf new file mode 100644 index 0000000..6278a75 --- /dev/null +++ b/terraform/locals.tf @@ -0,0 +1,13 @@ +locals { + resource_group_name = data.azurerm_resource_group.this.name + location = var.location == null ? data.azurerm_resource_group.this.location : var.location + + webapp_name_streamlit = "wa-cvbia-streamlit" + webapp_name_slackbot = "wa-cvbia-slackbot" + + key_vault_name = "kv-cvbia" + dropbox_app_token_secret_name = "dropbox-app-key" + dropbox_refresh_token_secret_name = "dropbox-refresh-token" + slack_bot_token = "slack-bot-token" + slack_app_token = "slack-app-token" +} diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..595e199 --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,151 @@ +data "azurerm_resource_group" "this" { + name = var.resource_group_name +} + +data "azurerm_client_config" "this" {} + +data "azurerm_key_vault" "this" { + name = local.key_vault_name + resource_group_name = local.resource_group_name +} + +data "azurerm_key_vault_secret" "dropbox_app_keyvault_secret" { + name = local.dropbox_app_token_secret_name + key_vault_id = data.azurerm_key_vault.this.id +} + +data "azurerm_key_vault_secret" "dropbox_refresh_token_keyvault_secret" { + name = local.dropbox_refresh_token_secret_name + key_vault_id = data.azurerm_key_vault.this.id +} + +data "azurerm_key_vault_secret" "slack_bot_token" { + name = local.slack_bot_token + key_vault_id = data.azurerm_key_vault.this.id +} + +data "azurerm_key_vault_secret" "slack_app_token" { + name = local.slack_app_token + key_vault_id = data.azurerm_key_vault.this.id +} + +resource "azuread_application" "this" { + display_name = "azuread-app-spcvbia-streamlit" + # "oauth2AllowIdTokenImplicitFlow": true, + # https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/Manifest/appId/6159b30e-4413-497b-bd20-95356e6ffd6e +} + +resource "azurerm_service_plan" "streamlit" { + name = "spcvbia-streamlit" + resource_group_name = local.resource_group_name + location = local.location + os_type = "Linux" + sku_name = "B1" +} + +resource "azurerm_linux_web_app" "streamlit" { + name = local.webapp_name_streamlit + resource_group_name = local.resource_group_name + location = local.location + service_plan_id = azurerm_service_plan.streamlit.id + + app_settings = { + WEBSITES_PORT : 80, + WEBSITES_CONTAINER_START_TIME_LIMIT : 1800, + DOCKER_ENABLE_CI : true, + DROPBOX_APP_KEY: data.azurerm_key_vault_secret.dropbox_app_keyvault_secret.value, + DROPBOX_REFRESH_TOKEN: data.azurerm_key_vault_secret.dropbox_refresh_token_keyvault_secret.value + } + + logs { + application_logs { + file_system_level = "Verbose" + } + http_logs { + file_system { + retention_in_days = 7 + retention_in_mb = 35 + } + } + } + + identity { + type = "SystemAssigned" + } + + site_config { + container_registry_use_managed_identity = true + + application_stack { + docker_image = var.docker_image_streamlit + docker_image_tag = "latest" + } + } + + auth_settings_v2 { + auth_enabled = true + unauthenticated_action = "Return401" + require_authentication = true + require_https = true + + active_directory_v2 { + client_id = azuread_application.this.application_id + client_secret_setting_name = "AzureAdClientSecret" # This should be allowed optional + tenant_auth_endpoint = "https://login.microsoftonline.com/${data.azurerm_client_config.this.tenant_id}" + } + + login { + token_store_enabled = true + } + } +} + +resource "azurerm_service_plan" "slackbot" { + name = "spcvbia-slackbot" + resource_group_name = local.resource_group_name + location = local.location + os_type = "Linux" + sku_name = "B1" +} + +resource "azurerm_linux_web_app" "slackbot" { + name = local.webapp_name_slackbot + resource_group_name = local.resource_group_name + location = local.location + service_plan_id = azurerm_service_plan.slackbot.id + + app_settings = { + WEBSITES_PORT : 8000, + WEBSITES_CONTAINER_START_TIME_LIMIT : 1800, + DOCKER_ENABLE_CI : true, + DROPBOX_APP_KEY: data.azurerm_key_vault_secret.dropbox_app_keyvault_secret.value, + DROPBOX_REFRESH_TOKEN: data.azurerm_key_vault_secret.dropbox_refresh_token_keyvault_secret.value, + SLACK_BOT_TOKEN: data.azurerm_key_vault_secret.slack_bot_token.value, + SLACK_APP_TOKEN: data.azurerm_key_vault_secret.slack_app_token.value + } + + logs { + application_logs { + file_system_level = "Verbose" + } + http_logs { + file_system { + retention_in_days = 7 + retention_in_mb = 35 + } + } + } + + identity { + type = "SystemAssigned" + } + + site_config { + container_registry_use_managed_identity = true + + application_stack { + docker_image = var.docker_image_slackbot + docker_image_tag = "latest" + } + } +} diff --git a/terraform/providers.tf b/terraform/providers.tf new file mode 100644 index 0000000..1ddd0b7 --- /dev/null +++ b/terraform/providers.tf @@ -0,0 +1,19 @@ +terraform { + required_version = ">= 1.3" + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = ">= 3.52" + } + } + backend "azurerm" { + resource_group_name = "cvbia_test" + storage_account_name = "sacvbia" + container_name = "tfstate" + key = "webapp-streamlit/terraform.tfstate" + } +} + +provider "azurerm" { + features {} +} diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..a1f4798 --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,29 @@ +variable "resource_group_name" { + type = string + description = "Name of the resource group" + default = "cvbia_test" +} + +variable "location" { + description = "Location of deployed resource. If not provided, the resource group location will be used" + type = string + default = null +} + +variable "docker_repository" { + type = string + default = "docker.io" + description = "Docker image name" +} + +variable "docker_image_streamlit" { + type = string + default = "cvbia.azurecr.io/cvbia_streamlit" + description = "Azure container registry image name" +} + +variable "docker_image_slackbot" { + type = string + default = "cvbia.azurecr.io/cvbia_slackbot" + description = "Azure container registry image name" +}