From f1374da61c32bc992d23c5510587b1ede185bf66 Mon Sep 17 00:00:00 2001 From: ravgeetdhillon Date: Sat, 10 Apr 2021 11:54:22 +0530 Subject: [PATCH 1/5] ci: added a release workflow --- .github/admins.txt | 1 + .github/workflows/main.yml | 35 -------------- .github/workflows/release.yml | 89 +++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 35 deletions(-) create mode 100644 .github/admins.txt delete mode 100644 .github/workflows/main.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/admins.txt b/.github/admins.txt new file mode 100644 index 00000000..5e3bf2a5 --- /dev/null +++ b/.github/admins.txt @@ -0,0 +1 @@ +ravgeetdhillon diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index a370e7a0..00000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Test - -on: - push: - branches: - - master - pull_request: - branches: - - master - -jobs: - test: - runs-on: ubuntu-latest - - env: - INPUT_STATUS: failure - INPUT_NOTIFY_WHEN: '["failure"]' - - steps: - - name: Clone the repository - uses: actions/checkout@v2 - - - name: Set up Python - uses: actions/setup-python@v1 - with: - python-version: '3.x' - - - name: Install PIP Dependencies - run: |- - python -m pip install --upgrade pip - pip install -r requirements.txt - - - name: Test Action - run: |- - python main.py --test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..1155635c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,89 @@ +name: Create Release + +on: + push: + branches: + - master + +jobs: + get-admins: + if: github.event.head_commit.message == 'do-production-release' + runs-on: ubuntu-latest + outputs: + admins: ${{ steps.admins.outputs.admins }} + steps: + - name: Dump Github Context + continue-on-error: true + run: | + printf "${{ toJson(github) }}" + + - name: Set up Repository + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Get Admins + id: admins + run: | + admins="|`tr '\n' '|' < .github/admins.txt`" + echo "::set-output name=admins::$admins" + echo "Admins: $admins" + + create-release: + needs: get-admins + if: github.event.head_commit.message == 'do-production-release' && contains(needs.get-admins.outputs.admins, format('|{0}|', github.event.head_commit.author.username)) + runs-on: ubuntu-latest + steps: + - name: Set up Repository + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Set up Node + uses: actions/setup-node@v2 + with: + node-version: 14 + + - name: Install Semantic Release NPM Dependencies + run: | + npm i -g semantic-release @semantic-release/{commit-analyzer,release-notes-generator} + + - name: Run Semantic Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + npx semantic-release --repository-url "https://github.com/$GITHUB_REPOSITORY" --branches master --tagFormat \${version} --plugins "@semantic-release/commit-analyzer" "@semantic-release/release-notes-generator" --no-ci --dry-run > temp.txt + + - name: Get Last Release Version + continue-on-error: true + run: | + last_version=`git describe --tags --abbrev=0` + echo "Last Version: $last_version" + echo "last_version=$last_version" >> $GITHUB_ENV + + - name: Get Next Release Version + run: | + next_version=`cat temp.txt | grep -oP 'Published release \K.*? ' | xargs` + echo "Next Version: $next_version" + echo "next_version=$next_version" >> $GITHUB_ENV + + - name: Cancel if Next Version is same as Last Version + if: env.last_version == env.next_version + uses: andymckay/cancel-action@0.2 + + - name: Get Release Notes + run: | + release_notes=`cat temp.txt | sed '/^##/,$!d' | awk '{$1=$1};1' | sed 's/))/)/g' | sed 's/ (h/](h/g' | sed 's/^## /## [/' | sed 's/) (/)(/g' | sed 's/ (/ [/g' | sed 's/)(/) (/g' | sed 's/\w/\u&/'` + printf "$release_notes" > RELEASE_NOTES.md + + - name: Create Release + uses: ncipollo/release-action@v1 + with: + name: ${{ env.next_version }} + tag: ${{ env.next_version }} + commit: master + bodyFile: RELEASE_NOTES.md + allowUpdates: false + prerelease: false + draft: false + token: ${{ secrets.GITHUB_TOKEN }} From 13b59f4f6b5cf57b003385d0194675af45606221 Mon Sep 17 00:00:00 2001 From: ravgeetdhillon Date: Sat, 10 Apr 2021 11:55:20 +0530 Subject: [PATCH 2/5] chore: updated README, fixed some documentation --- .pylintrc | 588 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 4 +- LICENSE | 2 +- README.md | 77 ++++++- 4 files changed, 663 insertions(+), 8 deletions(-) create mode 100644 .pylintrc diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 00000000..d2769570 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,588 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-whitelist= + +# Specify a score threshold to be exceeded before program exits with error. +fail-under=10.0 + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=print-statement, + parameter-unpacking, + unpacking-in-except, + old-raise-syntax, + backtick, + long-suffix, + old-ne-operator, + old-octal-literal, + import-star-module-level, + non-ascii-bytes-literal, + raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + apply-builtin, + basestring-builtin, + buffer-builtin, + cmp-builtin, + coerce-builtin, + execfile-builtin, + file-builtin, + long-builtin, + raw_input-builtin, + reduce-builtin, + standarderror-builtin, + unicode-builtin, + xrange-builtin, + coerce-method, + delslice-method, + getslice-method, + setslice-method, + no-absolute-import, + old-division, + dict-iter-method, + dict-view-method, + next-method-called, + metaclass-assignment, + indexing-exception, + raising-string, + reload-builtin, + oct-method, + hex-method, + nonzero-method, + cmp-method, + input-builtin, + round-builtin, + intern-builtin, + unichr-builtin, + map-builtin-not-iterating, + zip-builtin-not-iterating, + range-builtin-not-iterating, + filter-builtin-not-iterating, + using-cmp-argument, + eq-without-hash, + div-method, + idiv-method, + rdiv-method, + exception-message-attribute, + invalid-str-codec, + sys-max-int, + bad-python3-import, + deprecated-string-function, + deprecated-str-translate-call, + deprecated-itertools-function, + deprecated-types-field, + next-method-defined, + dict-items-not-iterating, + dict-keys-not-iterating, + dict-values-not-iterating, + deprecated-operator-function, + deprecated-urllib-function, + xreadlines-attribute, + deprecated-sys-function, + exception-escape, + comprehension-escape + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'error', 'warning', 'refactor', and 'convention' +# which contain the number of messages in each category, as well as 'statement' +# which is the total number of statements analyzed. This score is used by the +# global evaluation report (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +#notes-rgx= + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[DESIGN] + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled). +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled). +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception diff --git a/Dockerfile b/Dockerfile index c04c466d..6dca6102 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,10 +2,10 @@ FROM python:3-slim AS builder ADD . /app WORKDIR /app -# We are installing a dependency here directly into our app source dir +# we are installing a dependency here directly into our app source dir RUN pip install -r requirements.txt --target=/app -# A distroless container image with Python and some basics like SSL certificates +# a distroless container image with Python and some basics like SSL certificates # https://github.com/GoogleContainerTools/distroless FROM gcr.io/distroless/python3-debian10 COPY --from=builder /app /app diff --git a/LICENSE b/LICENSE index 7d4bca0d..bfd0a2f9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 RavSam Web Solutions +Copyright (c) 2021 RavSam Web Solutions Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index ad20e8f2..eb33d21a 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,88 @@ -![Test](https://github.com/ravsamhq/notify-slack-action/workflows/Test/badge.svg) +[![Create Release](https://github.com/ravsamhq/metafold-store-frontend/actions/workflows/release.yml/badge.svg)](https://github.com/ravsamhq/metafold-store-frontend/actions/workflows/release.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) # Notify Slack Action Send Github Actions workflow status notifications to Slack regarding failures, warnings or even success. You can read more about the action in [our blog post](https://www.ravsam.in/blog/send-slack-notification-when-github-actions-fails/). -### Example workflow +## Minimal workflow ```yaml steps: - uses: ravsamhq/notify-slack-action@master if: always() with: - status: ${{ job.status }} - notify_when: 'failure' # default is 'success,failure,warnings' + status: ${{ job.status }} # required env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} # required ``` -Made in Python • By [Ravgeet Dhillon](https://github.com/ravgeetdhillon) @ [RavSam Web Solutions](https://www.ravsam.in). +## Example workflow + +The following variables are available for formatting your own strings. + +- {branch} +- {commit_url} +- {commit_sha} +- {emoji} +- {repo} +- {repo_url} +- {status_message} +- {workflow} + +You can use these to construct custom `notification_title`, `message_format` and `footer`. To get an idea see the workflow below. + +```yaml +steps: + - uses: ravsamhq/notify-slack-action@master + if: always() + with: + status: ${{ job.status }} # required + notification_title: '{workflow} has {status_message}' # optional + message_format: '{emoji} *{workflow}* {status_message} in <{repo_url}|{repo}>' # optional + footer: 'Linked Repo <{repo_url}|{repo}>' # optional + notify_when: 'failure' # optional + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} # required +``` + +## Tech Stack + +- [Python](https://python.org/) - Programming +- [Slack Webhooks](https://slack.com/) - Notifications + +## Development + +Follow these instructions to get the project up and running. + +```bash +# clone the repo +git clone https://github.com/ravsamhq/notify-slack-action.git + +# change directory +cd notify-slack-action + +# setup python virtual environment +python3 -m venv venv + +# activate virtual environment +source venv/bin/activate + +# install pip dependencies +pip install -r requirements.txt +``` + +## Versioning + +This project uses [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/ravsamhq/notify-slack-action/tags). + +## Contributors + +- [Ravgeet Dhillon](https://github.com/ravgeetdhillon) + +## Extra + +- We are open for [issues and feature requests](https://github.com/ravsamhq/notify-slack-action/issues). +- In case you get stuck at somewhere, feel free to contact at [Mail](mailto:info@ravsam.in). + +© 2021 RavSam Web Solutions From 6e7b5d4303f83779496ec37ef0207671e18c1831 Mon Sep 17 00:00:00 2001 From: ravgeetdhillon Date: Sat, 10 Apr 2021 11:56:40 +0530 Subject: [PATCH 3/5] feat: added more action inputs to customize message format --- action.yml | 12 +++++ main.py | 131 ++++++++++++++++++++++++++++++++--------------- requirements.txt | 21 +++++--- 3 files changed, 117 insertions(+), 47 deletions(-) diff --git a/action.yml b/action.yml index 1c3b28d3..53ef7c1a 100644 --- a/action.yml +++ b/action.yml @@ -5,6 +5,18 @@ inputs: status: description: Job Status required: true + notification_title: + description: Specify on the notification message title + required: false + default: 'New Github Action Run' + message_format: + description: Specify on the notification message format + required: false + default: '{emoji} *{workflow}* {status_message} in <{repo_url}|{repo}@{branch}> on <{commit_url}|{commit_sha}>' + footer: + description: Specify the footer of the message + required: false + default: 'Developed by ' notify_when: description: Specify on which events a slack notification is sent required: false diff --git a/main.py b/main.py index bf62349a..fcb11f69 100644 --- a/main.py +++ b/main.py @@ -1,10 +1,15 @@ -import requests -import os +""" +A Github Action to send Github Actions workflow status notifications to Slack. +Main module for the app. +""" + import json -import sys +import os +import requests +from dotenv import load_dotenv -def actionColor(status): +def action_color(status): """ Get a action color based on the workflow status. """ @@ -13,11 +18,11 @@ def actionColor(status): return 'good' elif status == 'failure': return 'danger' + else: + return 'warning' - return 'warning' - -def actionStatus(status): +def action_status(status): """ Get a transformed status based on the workflow status. """ @@ -26,73 +31,119 @@ def actionStatus(status): return 'passed' elif status == 'failure': return 'failed' - - return 'passed with warnings' + else: + return 'passed with warnings' -def actionEmoji(status): +def action_emoji(status): """ Get an emoji based on the workflow status. """ if status == 'success': - return ':sunglasses:' + return ':heavy_check_mark:' elif status == 'failure': - return ':worried:' + return ':x:' + else: + return ':large_orange_diamond:' - return ':zipper_mouth_face:' +def construct_payload(inputs): + """ + Creates a message payload which can be sent to Slack. + """ -def notify_slack(job_status, notify_when): - url = os.getenv('SLACK_WEBHOOK_URL') + # derived from workflow environment workflow = os.getenv('GITHUB_WORKFLOW') repo = os.getenv('GITHUB_REPOSITORY') branch = os.getenv('GITHUB_REF') - commit = os.getenv('GITHUB_SHA') + commit_sha = os.getenv('GITHUB_SHA')[:7] - commit_url = f'https://github.com/{repo}/commit/{commit}' + # self constructed + commit_url = f'https://github.com/{repo}/commit/{commit_sha}' repo_url = f'https://github.com/{repo}/tree/{branch}' - - color = actionColor(job_status) - status_message = actionStatus(job_status) - emoji = actionEmoji(job_status) - - message = f'{emoji} {workflow} {status_message} in <{repo_url}|{repo}@{branch}> on <{commit_url}|{commit[:7]}>.' + color = action_color(inputs['job_status']) + status_message = action_status(inputs['job_status']) + emoji = action_emoji(inputs['job_status']) + + # construct notification title + title = inputs['notification_title'] + title = title.replace('{emoji}', emoji) + title = title.replace('{workflow}', workflow) + title = title.replace('{status_message}', status_message) + title = title.replace('{repo}', repo) + title = title.replace('{repo_url}', repo_url) + title = title.replace('{branch}', branch) + title = title.replace('{commit_url}', commit_url) + title = title.replace('{commit_sha}', commit_sha) + + # construct the message + message = inputs['message_format'] + message = message.replace('{emoji}', emoji) + message = message.replace('{workflow}', workflow) + message = message.replace('{status_message}', status_message) + message = message.replace('{repo}', repo) + message = message.replace('{repo_url}', repo_url) + message = message.replace('{branch}', branch) + message = message.replace('{commit_url}', commit_url) + message = message.replace('{commit_sha}', commit_sha) + + # construct the footer + footer = inputs['footer'] + footer = footer.replace('{emoji}', emoji) + footer = footer.replace('{workflow}', workflow) + footer = footer.replace('{status_message}', status_message) + footer = footer.replace('{repo}', repo) + footer = footer.replace('{repo_url}', repo_url) + footer = footer.replace('{branch}', branch) + footer = footer.replace('{commit_url}', commit_url) + footer = footer.replace('{commit_sha}', commit_sha) payload = { 'attachments': [ { 'text': message, - 'fallback': 'New Github Action Run', - 'pretext': 'New Github Action Run', + 'fallback': title, + 'pretext': title, 'color': color, 'mrkdwn_in': ['text'], - 'footer': 'Developed by ', + 'footer': footer, } ] } - payload = json.dumps(payload) + return json.dumps(payload) - headers = {'Content-Type': 'application/json'} - if notify_when is None: - notify_when = 'success,failure,warnings' +def notify_slack(payload, testing=False): + """ + Send a Slack notification. + """ - if job_status in notify_when and not testing: + if not testing: + headers = {'Content-Type': 'application/json'} + url = os.getenv('SLACK_WEBHOOK_URL') requests.post(url, data=payload, headers=headers) -def main(): - job_status = os.getenv('INPUT_STATUS') - notify_when = os.getenv('INPUT_NOTIFY_WHEN') - notify_slack(job_status, notify_when) +def main(testing=False): + """ + Main function for the app. + """ + inputs = { + 'job_status': os.getenv('INPUT_STATUS'), + 'notification_title': os.getenv('INPUT_NOTIFICATION_TITLE'), + 'message_format': os.getenv('INPUT_MESSAGE_FORMAT'), + 'footer': os.getenv('INPUT_FOOTER'), + 'notify_when': os.getenv('INPUT_NOTIFY_WHEN'), + } -if __name__ == '__main__': - try: - testing = True if sys.argv[1] == '--test' else False - except IndexError as e: - testing = False + payload = construct_payload(inputs) + if inputs['job_status'] in inputs['notify_when'] and not testing: + notify_slack(payload) + +if __name__ == '__main__': + load_dotenv() main() diff --git a/requirements.txt b/requirements.txt index 3cbaddba..2aa9d19a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,15 @@ -autopep8==1.5.3 -certifi==2020.6.20 -chardet==3.0.4 +astroid==2.5.2 +autopep8==1.5.6 +certifi==2020.12.5 +chardet==4.0.0 idna==2.10 -pycodestyle==2.6.0 -requests==2.24.0 -toml==0.10.1 -urllib3==1.25.9 +isort==5.8.0 +lazy-object-proxy==1.6.0 +mccabe==0.6.1 +pycodestyle==2.7.0 +pylint==2.7.4 +python-dotenv==0.17.0 +requests==2.25.1 +toml==0.10.2 +urllib3==1.26.4 +wrapt==1.12.1 From 22de3e89043b48bae9613f0362220179f1a62d2d Mon Sep 17 00:00:00 2001 From: ravgeetdhillon Date: Sat, 10 Apr 2021 13:34:16 +0530 Subject: [PATCH 4/5] feat: added option to mention users --- action.yml | 8 ++++++++ main.py | 24 ++++++++++++++++++------ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/action.yml b/action.yml index 53ef7c1a..61f7f1e0 100644 --- a/action.yml +++ b/action.yml @@ -21,6 +21,14 @@ inputs: description: Specify on which events a slack notification is sent required: false default: 'success,failure,warnings' + mention: + description: Specify the slack IDs of users you want to mention. + required: false + default: '' + mention_when: + description: Specify on which events you want to mention the users + required: false + default: 'success,failure,warnings' branding: icon: send diff --git a/main.py b/main.py index fcb11f69..a4258494 100644 --- a/main.py +++ b/main.py @@ -59,15 +59,22 @@ def construct_payload(inputs): branch = os.getenv('GITHUB_REF') commit_sha = os.getenv('GITHUB_SHA')[:7] + # derived from action inputs + job_status = inputs['job_status'] + message = inputs['message_format'] + title = inputs['notification_title'] + footer = inputs['footer'] + mention = inputs['mention'] + mention_when = inputs['mention_when'] + # self constructed commit_url = f'https://github.com/{repo}/commit/{commit_sha}' repo_url = f'https://github.com/{repo}/tree/{branch}' - color = action_color(inputs['job_status']) - status_message = action_status(inputs['job_status']) - emoji = action_emoji(inputs['job_status']) + color = action_color(job_status) + status_message = action_status(job_status) + emoji = action_emoji(job_status) # construct notification title - title = inputs['notification_title'] title = title.replace('{emoji}', emoji) title = title.replace('{workflow}', workflow) title = title.replace('{status_message}', status_message) @@ -78,7 +85,6 @@ def construct_payload(inputs): title = title.replace('{commit_sha}', commit_sha) # construct the message - message = inputs['message_format'] message = message.replace('{emoji}', emoji) message = message.replace('{workflow}', workflow) message = message.replace('{status_message}', status_message) @@ -88,8 +94,12 @@ def construct_payload(inputs): message = message.replace('{commit_url}', commit_url) message = message.replace('{commit_sha}', commit_sha) + # added mentions to the message + if job_status in mention_when: + for user_id in mention.split(','): + message = message + f' <@{user_id}>' + # construct the footer - footer = inputs['footer'] footer = footer.replace('{emoji}', emoji) footer = footer.replace('{workflow}', workflow) footer = footer.replace('{status_message}', status_message) @@ -137,6 +147,8 @@ def main(testing=False): 'message_format': os.getenv('INPUT_MESSAGE_FORMAT'), 'footer': os.getenv('INPUT_FOOTER'), 'notify_when': os.getenv('INPUT_NOTIFY_WHEN'), + 'mention': os.getenv('INPUT_MENTION'), + 'mention_when': os.getenv('INPUT_MENTION_WHEN'), } payload = construct_payload(inputs) From c9b730d1a757a39ddff8f052a56a731d899d48d7 Mon Sep 17 00:00:00 2001 From: ravgeetdhillon Date: Sat, 10 Apr 2021 13:37:42 +0530 Subject: [PATCH 5/5] chore: updated README with examples and screenshots --- README.md | 98 ++++++++++++++++++++++++++----- screenshots/minimal.png | Bin 0 -> 18592 bytes screenshots/with-mentions.png | Bin 0 -> 17214 bytes screenshots/without-mentions.png | Bin 0 -> 13690 bytes 4 files changed, 82 insertions(+), 16 deletions(-) create mode 100644 screenshots/minimal.png create mode 100644 screenshots/with-mentions.png create mode 100644 screenshots/without-mentions.png diff --git a/README.md b/README.md index eb33d21a..6326cc32 100644 --- a/README.md +++ b/README.md @@ -5,19 +5,45 @@ Send Github Actions workflow status notifications to Slack regarding failures, warnings or even success. You can read more about the action in [our blog post](https://www.ravsam.in/blog/send-slack-notification-when-github-actions-fails/). -## Minimal workflow - -```yaml -steps: - - uses: ravsamhq/notify-slack-action@master - if: always() - with: - status: ${{ job.status }} # required - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} # required +## Inputs + +```yml +status: + description: Job Status + required: true + +notification_title: + description: Specify on the notification message title + required: false + default: 'New Github Action Run' + +message_format: + description: Specify on the notification message format + required: false + default: '{emoji} *{workflow}* {status_message} in <{repo_url}|{repo}@{branch}> on <{commit_url}|{commit_sha}>' + +footer: + description: Specify the footer of the message + required: false + default: 'Developed by ' + +notify_when: + description: Specify on which events a slack notification is sent + required: false + default: 'success,failure,warnings' + +mention: + description: Specify the slack IDs of users you want to mention. + required: false + default: '' + +mention_when: + description: Specify on which events you want to mention the users + required: false + default: 'success,failure,warnings' ``` -## Example workflow +## Example workflows The following variables are available for formatting your own strings. @@ -32,20 +58,60 @@ The following variables are available for formatting your own strings. You can use these to construct custom `notification_title`, `message_format` and `footer`. To get an idea see the workflow below. +### Minimal workflow + +![](screenshots/minimal.png) + ```yaml steps: - uses: ravsamhq/notify-slack-action@master if: always() with: status: ${{ job.status }} # required - notification_title: '{workflow} has {status_message}' # optional - message_format: '{emoji} *{workflow}* {status_message} in <{repo_url}|{repo}>' # optional - footer: 'Linked Repo <{repo_url}|{repo}>' # optional - notify_when: 'failure' # optional env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} # required ``` +### Extended Example without Mentions + +![](screenshots/without-mentions.png) + +```yaml +steps: + - uses: ravsamhq/notify-slack-action@master + if: always() + with: + status: ${{ job.status }} + notification_title: '{workflow} has {status_message}' + message_format: '{emoji} *{workflow}* {status_message} in <{repo_url}|{repo}>' + footer: 'Linked Repo <{repo_url}|{repo}>' + notify_when: 'failure' + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} +``` + +### Extended Example with Mentions + +![](screenshots/with-mentions.png) + +```yaml +steps: + - uses: ravsamhq/notify-slack-action@master + if: always() + with: + status: ${{ job.status }} + notification_title: '{workflow} has {status_message}' + message_format: '{emoji} *{workflow}* {status_message} in <{repo_url}|{repo}>' + footer: 'Linked Repo <{repo_url}|{repo}>' + notify_when: 'failure' + mention: 'U0160UUNH8S,U0080UUAA9N' + mention_when: 'failure,warnings' + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} +``` + +> To get the Slack Member IDs, open the User profile you want to mention. Click *More* and *Copy Member ID*. + ## Tech Stack - [Python](https://python.org/) - Programming @@ -76,7 +142,7 @@ pip install -r requirements.txt This project uses [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/ravsamhq/notify-slack-action/tags). -## Contributors +## Authors - [Ravgeet Dhillon](https://github.com/ravgeetdhillon) diff --git a/screenshots/minimal.png b/screenshots/minimal.png new file mode 100644 index 0000000000000000000000000000000000000000..a7623e788412915ed76e05d9fb3a68d04784cbcd GIT binary patch literal 18592 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 TXWnlIRK(!v>gTe~DWM4f#}<|b literal 0 HcmV?d00001 diff --git a/screenshots/with-mentions.png b/screenshots/with-mentions.png new file mode 100644 index 0000000000000000000000000000000000000000..68a8fb3a63a1859914abee098d2c025bc976ed0e GIT binary patch literal 17214 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 literal 0 HcmV?d00001 diff --git a/screenshots/without-mentions.png b/screenshots/without-mentions.png new file mode 100644 index 0000000000000000000000000000000000000000..f5549c0e0078516a8ae12fb54ad24ea062d7e77e GIT binary patch literal 13690 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