diff --git a/sp2jira-cronjob/.dockerignore b/sp2jira-cronjob/.dockerignore new file mode 100644 index 0000000..199c8d7 --- /dev/null +++ b/sp2jira-cronjob/.dockerignore @@ -0,0 +1,36 @@ +# Include any files or directories that you don't want to be copied to your +# container here (e.g., local build artifacts, temporary files, etc.). +# +# For more help, visit the .dockerignore file reference guide at +# https://docs.docker.com/go/build-context-dockerignore/ + +**/.DS_Store +**/__pycache__ +**/.venv +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/bin +**/charts +**/docker-compose* +**/compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +**/sp2jiracron +LICENSE +README.md +**/sp2jira-cronjob diff --git a/sp2jira-cronjob/.gitignore b/sp2jira-cronjob/.gitignore new file mode 100644 index 0000000..254bd63 --- /dev/null +++ b/sp2jira-cronjob/.gitignore @@ -0,0 +1,3 @@ +__pycache__/JiraUtils.cpython-311.pyc +__pycache__/SharepointUtils.cpython-311.pyc +sp2jiracron/* diff --git a/sp2jira-cronjob/Dockerfile b/sp2jira-cronjob/Dockerfile new file mode 100644 index 0000000..2fe5280 --- /dev/null +++ b/sp2jira-cronjob/Dockerfile @@ -0,0 +1,49 @@ +# syntax=docker/dockerfile:1 + +# Comments are provided throughout this file to help you get started. +# If you need more help, visit the Dockerfile reference guide at +# https://docs.docker.com/go/dockerfile-reference/ + +ARG PYTHON_VERSION=3.12 +FROM python:${PYTHON_VERSION}-slim as base + +# Prevents Python from writing pyc files. +ENV PYTHONDONTWRITEBYTECODE=1 + +# Keeps Python from buffering stdout and stderr to avoid situations where +# the application crashes without emitting any logs due to buffering. +ENV PYTHONUNBUFFERED=1 + +WORKDIR /app + +# Create a non-privileged user that the app will run under. +# See https://docs.docker.com/go/dockerfile-user-best-practices/ +ARG UID=10001 +RUN adduser \ + --disabled-password \ + --gecos "" \ + --home "/nonexistent" \ + --shell "/sbin/nologin" \ + --no-create-home \ + --uid "${UID}" \ + appuser + +# Download dependencies as a separate step to take advantage of Docker's caching. +# Leverage a cache mount to /root/.cache/pip to speed up subsequent builds. +# Leverage a bind mount to requirements.txt to avoid having to copy them into +# into this layer. +RUN --mount=type=cache,target=/root/.cache/pip \ + --mount=type=bind,source=requirements.txt,target=requirements.txt \ + python -m pip install -r requirements.txt + +# Switch to the non-privileged user to run the application. +USER appuser + +# Copy the source code into the container. +COPY . . + +# Expose the port that the application listens on. +EXPOSE 8000 + +# Run the application. +CMD python Sp2jira.py diff --git a/sp2jira-cronjob/JiraUtils.py b/sp2jira-cronjob/JiraUtils.py new file mode 100644 index 0000000..b9d6786 --- /dev/null +++ b/sp2jira-cronjob/JiraUtils.py @@ -0,0 +1,31 @@ +from jira import JIRA + +class JiraUtils: + + # parameterized constructor + def __init__(self, jira_server, jira_auth_token, jira_project, jira_issue_type, jira_assignee, jira_watchers): + + self.jira = JIRA(server=jira_server, token_auth=jira_auth_token) + self.jira_project = jira_project + self.jira_issue_type = jira_issue_type + self.jira_assignee = jira_assignee + self.jira_watchers = jira_watchers + + def create_jira_issue_from_form_data(self, issue_summary, issue_desc): + + issue_dict = { + 'project': {'key': self.jira_project}, + 'summary': issue_summary, + # epic name field + 'customfield_10704': issue_summary, + 'description': issue_desc, + 'issuetype': {'name': self.jira_issue_type}, + 'assignee': {'name': self.jira_assignee} + } + + new_issue = self.jira.create_issue(fields=issue_dict) + # this can't be done as part of issue creation, unfortunately + for watcher in self.jira_watchers: + self.jira.add_watcher(new_issue, watcher) + + return new_issue diff --git a/sp2jira-cronjob/README.Docker.md b/sp2jira-cronjob/README.Docker.md new file mode 100644 index 0000000..6dae561 --- /dev/null +++ b/sp2jira-cronjob/README.Docker.md @@ -0,0 +1,22 @@ +### Building and running your application + +When you're ready, start your application by running: +`docker compose up --build`. + +Your application will be available at http://localhost:8000. + +### Deploying your application to the cloud + +First, build your image, e.g.: `docker build -t myapp .`. +If your cloud uses a different CPU architecture than your development +machine (e.g., you are on a Mac M1 and your cloud provider is amd64), +you'll want to build the image for that platform, e.g.: +`docker build --platform=linux/amd64 -t myapp .`. + +Then, push it to your registry, e.g. `docker push myregistry.com/myapp`. + +Consult Docker's [getting started](https://docs.docker.com/go/get-started-sharing/) +docs for more detail on building and pushing. + +### References +* [Docker's Python guide](https://docs.docker.com/language/python/) \ No newline at end of file diff --git a/sp2jira-cronjob/README.md b/sp2jira-cronjob/README.md new file mode 100644 index 0000000..3bf8af9 --- /dev/null +++ b/sp2jira-cronjob/README.md @@ -0,0 +1,39 @@ +# sp2jira-cron-container +- intended to be run as a k8s cronjob. +- retrieves ms forms submissions from a sharepoint excel file, and creates a jira issue +- keeps track of already-processed submissions via a Sharepoint List + +# TODO +- set up dev form / excel, dev list, dev jira project, test running both prod and dev with same container +- dockerfile / compose.yaml currently set up with defaults for a service, need to research what to change here for a cronjob but it's working + +## environment variables +The following environment variables are used by the script. + +### Jira +| Variable | Mandatory | Description | +| ----------- | ----------- | ----------- | +| `JIRA_SERVER` | * | URL of the jira server. e.g. https://jirab.statcan.ca | +| `JIRA_TOKEN` | * | the token used to authorize the script with the jira server | +| `JIRA_PROJECT` | * | the Jira project that tickets will be created in | +| `JIRA_ASSIGNEE` | * | the Jira user that Jira issues will be assigned to | +| `JIRA_WATCHERS` | * | json list of Jira users that will be added to new Jira issues as watchers. See https://stackoverflow.com/questions/31352317/how-to-pass-a-list-as-an-environment-variable | +| `JIRA_ISSUE_TYPE`| | the Jira project that tickets will be created in. Default is 'Epic' | +| `JIRA_ISSUE_SUMMARY`| | text for the issue summary. Defaults to 'DAS Intake Form submission by {0} {1}', where 0 is FNAME and 1 is LNAME | +| `JIRA_ISSUE_DESC_NO_RESPONSE`| | what to put when an answer hasn't been provided in a submission. Default is 'No Response' | + +### Sharepoint +| Variable | Mandatory | Description | +| ----------- | ----------- | ----------- | +| `SHAREPOINT_CLIENT_ID` | * | the id used to authorize the script with the sharepoint site | +| `SHAREPOINT_CLIENT_SECRET` | * | the secret used to authorize the script with the sharepoint site | +| `SHAREPOINT_SITE_URL` | * | URL of the sharepoint site | +| `SHAREPOINT_FILE_URL` | * | Path to the .xslx file in sharepoint. | +| `SHAREPOINT_LIST_TITLE`| * | Name of the processed id list in sharepoint. | +| `SHAREPOINT_SHEET_NAME` | | Name of the excel sheet used. Default is 'Form1' | +| `SHAREPOINT_ID_COLUMN` | | Name of the excel sheet used. Default is '0' | +| `SHAREPOINT_FNAME_COLUMN` | | Name of the column containing First Name. Default is 'First name' | +| `SHAREPOINT_LNAME_COLUMN` | | Name of the column containing Last Name. Default is 'Last name' | +| `SHAREPOINT_LIST_COLUMN`| | Name of the list column in sharepoint containing processed ID data. Default is 'Title' | +| `SHAREPOINT_LIST_MAX_RETURN`| | Maximum number of list items to fetch. Default is '5000' | + diff --git a/sp2jira-cronjob/SharepointUtils.py b/sp2jira-cronjob/SharepointUtils.py new file mode 100644 index 0000000..7f5b897 --- /dev/null +++ b/sp2jira-cronjob/SharepointUtils.py @@ -0,0 +1,63 @@ +from office365.runtime.auth.client_credential import ClientCredential +from office365.sharepoint.client_context import ClientContext +from office365.sharepoint.files.file import File +import io +import pandas as pd + +class SharepointUtils: + + # creates the sharepoint connection + def __init__(self, client_id, client_secret, site_url, file_url, sheet_name, list_title, list_column, list_max_return): + + client_creds = ClientCredential(client_id, client_secret) + self.ctx = ClientContext(site_url).with_credentials(client_creds) + self.file_url = file_url + self.sheet_name = sheet_name + self.list_title = list_title + self.list_column = list_column + self.list_max_return = list_max_return + + + def get_intake_form_data_as_dataframe(self): + + # connect to sharepoint and get the xslx file + response = File.open_binary(self.ctx, self.file_url) + + # save data to BytesIO stream + bytes_file_obj = io.BytesIO() + bytes_file_obj.write(response.content) + bytes_file_obj.seek(0) #set file object to start + + # read excel file and each sheet into pandas dataframe + df = pd.read_excel(bytes_file_obj, self.sheet_name) + # drop empty rows. inplace=True modifies the existing dataframe instead of returning a new one. + df.dropna(inplace=True, subset=['ID']) + # remove the already processed ids from the dataframe + processed_ids = self.get_processed_id_list() + df = df[df.ID.isin(processed_ids) == False] + + return df + + def get_processed_id_list(self): + + # connect to sharepoint and get the list of IDs + raw_list = self.ctx.web.lists.get_by_title(self.list_title) + id_list = raw_list.items.get().select([self.list_column]).top(self.list_max_return).execute_query() + + print("Total number of processed applications before this run: {0}".format(len(id_list))) + processed_id_list = [] + + for index, item in enumerate(id_list): # type: int, ListItem + application_id = float(item.properties[self.list_column]) #convert to float to match dataframe + processed_id_list.append(application_id) + + return processed_id_list + + def add_processed_id_to_list(self, new_id): + + raw_list = self.ctx.web.lists.get_by_title(self.list_title) + new_list_item_properties = { + self.list_column: str(new_id) #need to convert back to string because sharepoint wants it that way + } + new_item = raw_list.add_item(new_list_item_properties).execute_query() + return new_item diff --git a/sp2jira-cronjob/Sp2jira.py b/sp2jira-cronjob/Sp2jira.py new file mode 100644 index 0000000..c5788c3 --- /dev/null +++ b/sp2jira-cronjob/Sp2jira.py @@ -0,0 +1,70 @@ +import os +import json +import pandas as pd +from JiraUtils import JiraUtils +from SharepointUtils import SharepointUtils + +# initialize JIRA variables and helper class. see README for details. +# mandatories +jira_server = os.environ["JIRA_SERVER"] #"https://jirab.statcan.ca" +jira_auth_token = os.environ["JIRA_TOKEN"] +jira_project = os.environ["JIRA_PROJECT"] #"DASBOP" +jira_assignee = os.environ["JIRA_ASSIGNEE"] #"luodan" +jira_watchers = json.loads(os.environ['JIRA_WATCHERS']) #["zimmshe", "bonedan", "coutann"] https://stackoverflow.com/questions/31352317/how-to-pass-a-list-as-an-environment-variable +#optionals +jira_issue_type = os.environ.get('JIRA_ISSUE_TYPE', "Epic") +jira_issue_summary = os.environ.get('JIRA_ISSUE_SUMMARY', "DAS Intake Form submission by {0} {1}") +jira_desc_no_response = os.environ.get('JIRA_ISSUE_DESC_NO_RESPONSE', "No Response") + +jira = JiraUtils(jira_server, jira_auth_token, jira_project, jira_issue_type, jira_assignee, jira_watchers) + +# initialize sharepoint variables and helper class. see README for details. +#mandatories +client_id = os.environ['SHAREPOINT_CLIENT_ID'] +client_secret = os.environ['SHAREPOINT_CLIENT_SECRET'] +site_url = os.environ['SHAREPOINT_SITE_URL'] #"https://054gc.sharepoint.com/sites/DAaaSD-AllStaff-DADS-Touslesemployes" +file_url = os.environ['SHAREPOINT_FILE_URL'] #"/sites/DAaaSD-AllStaff-DADS-Touslesemployes/Shared%20Documents/CSU%20-%20UCS/DAaaS%20Intake%20Form/Data%20Analytics%20Services%20(DAS)%20-%20Get%20started%201.xlsx" +list_title = os.environ['SHAREPOINT_LIST_TITLE'] #"Intake_form_processed_ids" +#optionals +sheet_name = os.environ.get('SHAREPOINT_SHEET_NAME', "Form1") +ID_COL = os.environ.get('SHAREPOINT_ID_COLUMN', 0) +FNAME_COL = os.environ.get('SHAREPOINT_FNAME_COLUMN', "First name") +LNAME_COL = os.environ.get('SHAREPOINT_LNAME_COLUMN', "Last name") +list_column = os.environ.get('SHAREPOINT_LIST_COLUMN', "Title" ) +list_max_return = os.environ.get('SHAREPOINT_LIST_MAX_RETURN', 5000) #if we ever get more applications than this we'll have to adjust it + +sputils = SharepointUtils(client_id, client_secret, site_url, file_url, sheet_name, list_title, list_column, list_max_return) + + +# get the form data from sharepoint +df = sputils.get_intake_form_data_as_dataframe() + +## go through each row and create a JIRA issue, saving processed IDs to the sharepoint list so we don't create them again later +issue_count = 0 +for index, row in df.iterrows(): + + current_id = row[ID_COL] + issue_desc = "" + issue_summary = jira_issue_summary.format(row[FNAME_COL], row[LNAME_COL]) + + for rowindex, rowval in row.items(): + issue_desc += f"{rowindex} : \n" + if pd.isna(rowval): + issue_desc += f"*{jira_desc_no_response}*\n\n" + else: + issue_desc += f"*{rowval}*\n\n" + + print(f"JIRA issue to be created from row id: {current_id}") + print(f"Summary: {issue_summary}") + #print(issue_desc) #left for debug + + try: + new_issue = jira.create_jira_issue_from_form_data(issue_summary, issue_desc) + except: + print(f"Error creating JIRA issue from ID {current_id}") + else: + sputils.add_processed_id_to_list(current_id) + issue_count += 1 + print(new_issue) + +print(f"Process completed. {issue_count} issues created.") diff --git a/sp2jira-cronjob/compose.yaml b/sp2jira-cronjob/compose.yaml new file mode 100644 index 0000000..9574be2 --- /dev/null +++ b/sp2jira-cronjob/compose.yaml @@ -0,0 +1,35 @@ +# Comments are provided throughout this file to help you get started. +# If you need more help, visit the Docker compose reference guide at +# https://docs.docker.com/go/compose-spec-reference/ + +# Here the instructions define your application as a service called "server". +# This service is built from the Dockerfile in the current directory. +# You can add other services your application may depend on here, such as a +# database or a cache. For examples, see the Awesome Compose repository: +# https://github.com/docker/awesome-compose +services: + server: + build: + context: . + ports: + - 8000:8000 + environment: + - JIRA_SERVER + - JIRA_TOKEN + - JIRA_PROJECT + - JIRA_ASSIGNEE + - JIRA_WATCHERS + - JIRA_ISSUE_TYPE + - JIRA_ISSUE_SUMMARY + - JIRA_ISSUE_DESC_NO_RESPONSE + - SHAREPOINT_CLIENT_ID + - SHAREPOINT_CLIENT_SECRET + - SHAREPOINT_SITE_URL + - SHAREPOINT_FILE_URL + - SHAREPOINT_LIST_TITLE + - SHAREPOINT_SHEET_NAME + - SHAREPOINT_ID_COLUMN + - SHAREPOINT_FNAME_COLUMN + - SHAREPOINT_LNAME_COLUMN + - SHAREPOINT_LIST_COLUMN + - SHAREPOINT_LIST_MAX_RETURN diff --git a/sp2jira-cronjob/requirements.txt b/sp2jira-cronjob/requirements.txt new file mode 100644 index 0000000..f1a69a7 --- /dev/null +++ b/sp2jira-cronjob/requirements.txt @@ -0,0 +1,26 @@ +certifi==2023.11.17 +cffi==1.16.0 +charset-normalizer==3.3.2 +cryptography==41.0.7 +defusedxml==0.7.1 +et-xmlfile==1.1.0 +idna==3.6 +jira==3.5.2 +msal==1.26.0 +numpy==1.26.3 +oauthlib==3.2.2 +Office365-REST-Python-Client==2.5.4 +openpyxl==3.1.2 +packaging==23.2 +pandas==2.1.4 +pycparser==2.21 +PyJWT==2.8.0 +python-dateutil==2.8.2 +pytz==2023.3.post1 +requests==2.31.0 +requests-oauthlib==1.3.1 +requests-toolbelt==1.0.0 +six==1.16.0 +typing_extensions==4.9.0 +tzdata==2023.4 +urllib3==2.1.0 diff --git a/sp2jira-cronjob/sp2jira-cronjob/.helmignore b/sp2jira-cronjob/sp2jira-cronjob/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/sp2jira-cronjob/sp2jira-cronjob/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/sp2jira-cronjob/sp2jira-cronjob/Chart.yaml b/sp2jira-cronjob/sp2jira-cronjob/Chart.yaml new file mode 100644 index 0000000..4665532 --- /dev/null +++ b/sp2jira-cronjob/sp2jira-cronjob/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: sp2jira-cronjob +description: A Helm chart for the utility project sharepoint to jira + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/sp2jira-cronjob/sp2jira-cronjob/templates/NOTES.txt b/sp2jira-cronjob/sp2jira-cronjob/templates/NOTES.txt new file mode 100644 index 0000000..b40e15f --- /dev/null +++ b/sp2jira-cronjob/sp2jira-cronjob/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "sp2jira-cronjob.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "sp2jira-cronjob.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "sp2jira-cronjob.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "sp2jira-cronjob.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/sp2jira-cronjob/sp2jira-cronjob/templates/_helpers.tpl b/sp2jira-cronjob/sp2jira-cronjob/templates/_helpers.tpl new file mode 100644 index 0000000..531974e --- /dev/null +++ b/sp2jira-cronjob/sp2jira-cronjob/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "sp2jira-cronjob.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "sp2jira-cronjob.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "sp2jira-cronjob.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "sp2jira-cronjob.labels" -}} +helm.sh/chart: {{ include "sp2jira-cronjob.chart" . }} +{{ include "sp2jira-cronjob.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "sp2jira-cronjob.selectorLabels" -}} +app.kubernetes.io/name: {{ include "sp2jira-cronjob.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "sp2jira-cronjob.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "sp2jira-cronjob.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/sp2jira-cronjob/sp2jira-cronjob/templates/cronjob.yaml b/sp2jira-cronjob/sp2jira-cronjob/templates/cronjob.yaml new file mode 100644 index 0000000..678026e --- /dev/null +++ b/sp2jira-cronjob/sp2jira-cronjob/templates/cronjob.yaml @@ -0,0 +1,116 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: sp2jira-cronjob +spec: + concurrencyPolicy: {{ .Values.job.concurrencyPolicy }} + successfulJobsHistoryLimit: {{ .Values.job.successfulJobsHistoryLimit }} + failedJobsHistoryLimit: {{ .Values.job.failedJobsHistoryLimit }} + schedule: "{{ .Values.job.schedule }}" + jobTemplate: + spec: # See k8s API JobSpec + template: # See k8s API PodTemplateSpec + metadata: + name: sp2jira-cronjob + spec: # See k8s API PodSpec + restartPolicy: {{ .Values.job.restartPolicy }} + containers: + - name: sp2jira-cronjob + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + env: + - name: JIRA_SERVER + valueFrom : + secretKeyRef: + name: {{.Values.secretName}} + key: jiraServer + - name: JIRA_TOKEN + valueFrom : + secretKeyRef: + name: {{.Values.secretName}} + key: jiraToken + - name: JIRA_PROJECT + valueFrom : + secretKeyRef: + name: {{.Values.secretName}} + key: jiraProject + - name: JIRA_ASSIGNEE + valueFrom : + secretKeyRef: + name: {{.Values.secretName}} + key: jiraAssignee + - name: JIRA_WATCHERS + valueFrom : + secretKeyRef: + name: {{.Values.secretName}} + key: jiraWatchers + - name: JIRA_ISSUE_TYPE + valueFrom : + secretKeyRef: + name: {{.Values.secretName}} + key: jiraIssueType + - name: JIRA_ISSUE_SUMMARY + valueFrom : + secretKeyRef: + name: {{.Values.secretName}} + key: jiraIssueSummary + - name: JIRA_ISSUE_DESC_NO_RESPONSE + valueFrom : + secretKeyRef: + name: {{.Values.secretName}} + key: jiraIssueDescNoResponse + - name: SHAREPOINT_CLIENT_ID + valueFrom : + secretKeyRef: + name: {{.Values.secretName}} + key: sharepointClientId + - name: SHAREPOINT_CLIENT_SECRET + valueFrom : + secretKeyRef: + name: {{.Values.secretName}} + key: sharepointClientSecret + - name: SHAREPOINT_SITE_URL + valueFrom : + secretKeyRef: + name: {{.Values.secretName}} + key: sharepointSiteUrl + - name: SHAREPOINT_FILE_URL + valueFrom : sharepointFileUrl + secretKeyRef: + name: {{.Values.secretName}} + key: sharepointFileUrl + - name: SHAREPOINT_LIST_TITLE + valueFrom : + secretKeyRef: + name: {{.Values.secretName}} + key: sharepointListTitle + - name: SHAREPOINT_SHEET_NAME + valueFrom : + secretKeyRef: + name: {{.Values.secretName}} + key: sharepointSheetName + - name: SHAREPOINT_ID_COLUMN + valueFrom : + secretKeyRef: + name: {{.Values.secretName}} + key: sharepointIdColumn + - name: SHAREPOINT_FNAME_COLUMN + valueFrom : + secretKeyRef: + name: {{.Values.secretName}} + key: sharepointFnameColumn + - name: SHAREPOINT_LNAME_COLUMN + valueFrom : + secretKeyRef: + name: {{.Values.secretName}} + key: sharepointLnameColumn + - name: SHAREPOINT_LIST_COLUMN + valueFrom : + secretKeyRef: + name: {{.Values.secretName}} + key: sharepointListColumn + - name: SHAREPOINT_LIST_MAX_RETURN + valueFrom : + secretKeyRef: + name: {{.Values.secretName}} + key: sharepointListMaxReturn diff --git a/sp2jira-cronjob/sp2jira-cronjob/templates/secrets.yaml b/sp2jira-cronjob/sp2jira-cronjob/templates/secrets.yaml new file mode 100644 index 0000000..6c01ec8 --- /dev/null +++ b/sp2jira-cronjob/sp2jira-cronjob/templates/secrets.yaml @@ -0,0 +1,25 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{.Values.secretName}} +data: + jiraServer: "{{.Values.environmentVariables.jiraServer}}" + jiraToken: "{{".Values.environmentVariables.jiraToken"}}" + jiraProject: "{{.Values.environmentVariables.jiraProject}}" + jiraAssignee: "{{.Values.environmentVariables.jiraAssignee}}" + jiraWatchers: {{.Values.environmentVariables.jiraWatchers}} + jiraIssueType: "{{.Values.environmentVariables.jiraIssueType}}" + jiraIssueSummary: "{{.Values.environmentVariables.jiraIssueSummary}}" + jiraIssueDescNoResponse: "{{.Values.environmentVariables.jiraIssueDescNoResponse}}" + sharepointClientId: ".Values.environmentVariables.sharepointClientId" + sharepointClientSecret: "{{.Values.environmentVariables.sharepointClientSecret}}" + sharepointSiteUrl: "{{.Values.environmentVariables.sharepointSiteUrl}}" + sharepointFileUrl: "{{.Values.environmentVariables.sharepointFileUrl}}" + sharepointListTitle: "{{.Values.environmentVariables.sharepointListTitle}}" + sharepointSheetName: "{{.Values.environmentVariables.sharepointSheetName}}" + sharepointIdColumn: {{.Values.environmentVariables.sharepointIdColumn}} + sharepointFnameColumn: "{{.Values.environmentVariables.sharepointFnameColumn}}" + sharepointLnameColumn: "{{.Values.environmentVariables.sharepointLnameColumn}}" + sharepointListColumn: "{{.Values.environmentVariables.sharepointListColumn}}" + sharepointListMaxReturn: {{.Values.environmentVariables.sharepointListMaxReturn}} + \ No newline at end of file diff --git a/sp2jira-cronjob/sp2jira-cronjob/templates/service.yaml b/sp2jira-cronjob/sp2jira-cronjob/templates/service.yaml new file mode 100644 index 0000000..bb90cbc --- /dev/null +++ b/sp2jira-cronjob/sp2jira-cronjob/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "sp2jira-cronjob.fullname" . }} + labels: + {{- include "sp2jira-cronjob.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "sp2jira-cronjob.selectorLabels" . | nindent 4 }} diff --git a/sp2jira-cronjob/sp2jira-cronjob/templates/serviceaccount.yaml b/sp2jira-cronjob/sp2jira-cronjob/templates/serviceaccount.yaml new file mode 100644 index 0000000..aec12a1 --- /dev/null +++ b/sp2jira-cronjob/sp2jira-cronjob/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "sp2jira-cronjob.serviceAccountName" . }} + labels: + {{- include "sp2jira-cronjob.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/sp2jira-cronjob/sp2jira-cronjob/templates/tests/test-connection.yaml b/sp2jira-cronjob/sp2jira-cronjob/templates/tests/test-connection.yaml new file mode 100644 index 0000000..577172d --- /dev/null +++ b/sp2jira-cronjob/sp2jira-cronjob/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "sp2jira-cronjob.fullname" . }}-test-connection" + labels: + {{- include "sp2jira-cronjob.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "sp2jira-cronjob.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/sp2jira-cronjob/sp2jira-cronjob/values.yaml b/sp2jira-cronjob/sp2jira-cronjob/values.yaml new file mode 100644 index 0000000..e0858d5 --- /dev/null +++ b/sp2jira-cronjob/sp2jira-cronjob/values.yaml @@ -0,0 +1,113 @@ +# Default values for sp2jira-cronjob. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: aaw-geo/sp2jira #? + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +job: + failedJobsHistoryLimit: 1 + successfulJobsHistoryLimit: 2 + concurrencyPolicy: Forbid + restartPolicy: OnFailure + # schedule: "*/15 * * * *" ## Every 15 mn + schedule: "0 * * * *" #=> Every hour + +secretName: sp2jira-secret +environmentVariables: + jiraServer: "" + jiraToken: "" + jiraProject: "" + jiraAssignee: "" + jiraWatchers: "" + jiraIssueType: "" + jiraIssueSummary: "" + jiraIssueDescNoResponse: "" + sharepointClientId: "" + sharepointClientSecret: "" + sharepointSiteUrl: "" + sharepointFileUrl: "" + sharepointListTitle: "" + sharepointSheetName: "" + sharepointIdColumn: "" + sharepointFnameColumn: "" + sharepointLnameColumn: "" + sharepointListColumn: "" + sharepointListMaxReturn: "" + + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 80 + +ingress: + enabled: false + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {}