diff --git a/feature1/README.md b/feature1/README.md new file mode 100644 index 0000000..00f1ad8 --- /dev/null +++ b/feature1/README.md @@ -0,0 +1,12 @@ +# Clock Exercise Solution + +#### 1. Code to perform the calculation - Please see file main.py +#### 2. How will you deploy this solution (in code or as a todo list if time is limited). i.e. how and where will this run? - Deployment will be done by Terraform +#### 3. How will you manage any infrastructure needed? - All GCP services used in the solution are Serverless so infrastruction management is not necessary +#### 4. Delivered as a feature branch in the repo fork - All code is available in the branch 'feature1' +#### 5. Bonus points for a working deployed solution in GCP that you can demo at the "sprint review" (ie interview) - A working prototype has been deployed on GCP +#### 6. Any DevOps/Cicd components that would support this feature in a production setting - Please see the workflow diagram below + + + +![Workflow](https://github.com/pravin-svn/clocks/blob/feature1/feature1/workflow.png) diff --git a/feature1/cloudbuild.yaml b/feature1/cloudbuild.yaml new file mode 100644 index 0000000..e5441df --- /dev/null +++ b/feature1/cloudbuild.yaml @@ -0,0 +1,18 @@ +steps: +- name: 'python:3.7-slim' + id: Test + entrypoint: /bin/sh + args: + - -c + - 'pip install google-cloud-datastore && python3 test.py' +- name: 'gcr.io/cloud-builders/gcloud' + id: Deploy + args: + - functions + - deploy + - cf_get_angle + - --source=. + - --trigger-topic=topic_time_value + - --runtime=python37 + - --entry-point=cf_get_angle + diff --git a/feature1/gcb_slack/index.js b/feature1/gcb_slack/index.js new file mode 100644 index 0000000..4a70116 --- /dev/null +++ b/feature1/gcb_slack/index.js @@ -0,0 +1,46 @@ +const { IncomingWebhook } = require('@slack/webhook'); +const url = process.env.SLACK_WEBHOOK_URL; + +const webhook = new IncomingWebhook(url); + +// subscribeSlack is the main function called by Cloud Functions. +module.exports.subscribeSlack = (pubSubEvent, context) => { + const build = eventToBuild(pubSubEvent.data); + + // Skip if the current status is not in the status list. + // Add additional statuses to list if you'd like: + // QUEUED, WORKING, SUCCESS, FAILURE, + // INTERNAL_ERROR, TIMEOUT, CANCELLED + const status = ['SUCCESS', 'FAILURE', 'INTERNAL_ERROR', 'TIMEOUT']; + if (status.indexOf(build.status) === -1) { + return; + } + + // Send message to Slack. + const message = createSlackMessage(build); + webhook.send(message); +}; + +// eventToBuild transforms pubsub event message to a build object. +const eventToBuild = (data) => { + return JSON.parse(Buffer.from(data, 'base64').toString()); +} + +// createSlackMessage creates a message from a build object. +const createSlackMessage = (build) => { + const message = { + text: `Build \`${build.id}\``, + mrkdwn: true, + attachments: [ + { + title: 'Build logs for cf_get_angle', + title_link: build.logUrl, + fields: [{ + title: 'Status', + value: build.status + }] + } + ] + }; + return message; +} \ No newline at end of file diff --git a/feature1/gcb_slack/package.json b/feature1/gcb_slack/package.json new file mode 100644 index 0000000..047f9c2 --- /dev/null +++ b/feature1/gcb_slack/package.json @@ -0,0 +1,9 @@ +{ + "name": "google-container-slack", + "version": "0.0.1", + "description": "Slack integration for Google Cloud Build, using Google Cloud Functions", + "main": "index.js", + "dependencies": { + "@slack/webhook": "5.0.1" + } +} \ No newline at end of file diff --git a/feature1/main.py b/feature1/main.py new file mode 100644 index 0000000..bb2099e --- /dev/null +++ b/feature1/main.py @@ -0,0 +1,64 @@ +#from google.cloud import pubsub_v1 +from google.cloud import datastore +import datetime +import base64 + +client = datastore.Client('pravin-clock-exercise') + + +def getAngles(t): + + hh, mm = map(int, t.split(':')) + + # validate the input + if (hh < 0 or mm < 0 or hh > 24 or mm > 60): + print('The input for time value is Invalid') + + if (hh >= 12): + hh = hh - 12 + if (mm == 60): + mm = 0 + + # The minute hand moves 360 degree in 60 minute(or 6 degree in one minute) + # and hour hand moves 360 degree in 12 hours(or 0.5 degree in 1 minute). + # In h hours and m minutes, the minute hand would move (h*60 + m)*6 + # and hour hand would move (h*60 + m)*0.5. + + hour_angle = 0.5 * (hh * 60 + mm) + minute_angle = 6 * mm + + # Find the difference between two angles + angle = abs(hour_angle - minute_angle) + + # Return the smaller angle of two possible angles + angle = min(360 - angle, angle) + + return angle + + +def cf_get_angle(event, context): + + ''' + if context.event_type == 'google.pubsub.topic.publish': + str_time = base64.b64decode(event['data']).decode('utf-8') + else: + str_time = event['data'] + ''' + str_time = event['data'] + angle = int(getAngles(str_time)) + result = f"Angle for time value {str_time} is {angle} degrees" + print(result) + + key = client.key('ClockAngles') + + ds_table = datastore.Entity( + key, exclude_from_indexes=['result']) + + ds_table.update({ + 'created': datetime.datetime.utcnow(), + 'result': result, + 'input': str_time, + 'angle': angle + }) + + client.put(ds_table) diff --git a/feature1/requirements.txt b/feature1/requirements.txt new file mode 100644 index 0000000..1c92ec1 --- /dev/null +++ b/feature1/requirements.txt @@ -0,0 +1 @@ +google-cloud-datastore \ No newline at end of file diff --git a/feature1/test.py b/feature1/test.py new file mode 100644 index 0000000..ae27c97 --- /dev/null +++ b/feature1/test.py @@ -0,0 +1,12 @@ +import unittest +import main + + +class TestMain(unittest.TestCase): + def test_case_1(self): + time_value = "03:00" + result = main.getAngles(time_value) + self.assertEqual(result, 90) + + +unittest.main() \ No newline at end of file diff --git a/feature1/workflow.pdf b/feature1/workflow.pdf new file mode 100644 index 0000000..365961f Binary files /dev/null and b/feature1/workflow.pdf differ diff --git a/feature1/workflow.png b/feature1/workflow.png new file mode 100644 index 0000000..8292284 Binary files /dev/null and b/feature1/workflow.png differ