Skip to content

Commit

Permalink
Merge pull request #7 from bcochofel/azuredevops
Browse files Browse the repository at this point in the history
feat(azuredevops): added azure devops output
  • Loading branch information
bcochofel authored Jan 31, 2021
2 parents 23762cc + bff762a commit 3991a93
Show file tree
Hide file tree
Showing 5 changed files with 296 additions and 13 deletions.
34 changes: 33 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,40 @@
## [Unreleased]


<a name="0.4.0"></a>
## [0.4.0] - 2021-01-31
### Feat
- **azuredevops:** send request
- **azuredevops:** added azure devops output


<a name="0.3.4"></a>
## [0.3.4] - 2021-01-30
### Chore
- remove requirements.txt


<a name="0.3.3"></a>
## [0.3.3] - 2021-01-30
### Chore
- update build information


<a name="0.3.2"></a>
## [0.3.2] - 2021-01-30
### Docs
- update docs with usage


<a name="0.3.1"></a>
## [0.3.1] - 2021-01-30
### Docs
- update setup classifier
- update module documentation

### Pull Requests
- Merge pull request [#6](https://github.com/bcochofel/terraplanfeed/issues/6) from bcochofel/update_stdout_doc


<a name="0.3.0"></a>
## [0.3.0] - 2021-01-30
Expand Down Expand Up @@ -91,7 +119,11 @@
- Merge pull request [#1](https://github.com/bcochofel/terraplanfeed/issues/1) from bcochofel/package_skel


[Unreleased]: https://github.com/bcochofel/terraplanfeed/compare/0.3.1...HEAD
[Unreleased]: https://github.com/bcochofel/terraplanfeed/compare/0.4.0...HEAD
[0.4.0]: https://github.com/bcochofel/terraplanfeed/compare/0.3.4...0.4.0
[0.3.4]: https://github.com/bcochofel/terraplanfeed/compare/0.3.3...0.3.4
[0.3.3]: https://github.com/bcochofel/terraplanfeed/compare/0.3.2...0.3.3
[0.3.2]: https://github.com/bcochofel/terraplanfeed/compare/0.3.1...0.3.2
[0.3.1]: https://github.com/bcochofel/terraplanfeed/compare/0.3.0...0.3.1
[0.3.0]: https://github.com/bcochofel/terraplanfeed/compare/0.2.0...0.3.0
[0.2.0]: https://github.com/bcochofel/terraplanfeed/compare/0.1.3...0.2.0
Expand Down
2 changes: 1 addition & 1 deletion terraplanfeed/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
Modules:
terraform
"""
__version__ = "0.3.1"
__version__ = "0.4.0"
233 changes: 233 additions & 0 deletions terraplanfeed/azuredevops.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,243 @@
"""
Azure DevOps module for Pull Request comment with changes.
This module expects the following ENV variables:
- SYSTEM_TEAMFOUNDATIONSERVERURI
- SYSTEM_TEAMPROJECT
- BUILD_REPOSITORY_ID
- SYSTEM_PULLREQUEST_PULLREQUESTID
- SYSTEM_ACCESSTOKEN
if any of these variables is empty it will use stdout driver.
Some useful links:
https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#system-variables-devops-services
https://docs.microsoft.com/en-us/rest/api/azure/devops/git/pull%20request%20threads/create?view=azure-devops-rest-6.0
https://docs.microsoft.com/en-us/rest/api/azure/devops/git/pull%20request%20threads/create?view=azure-devops-rest-6.0#commentthreadstatus
https://docs.microsoft.com/en-us/rest/api/azure/devops/git/pull%20request%20threads/create?view=azure-devops-rest-6.0#comment
https://docs.microsoft.com/en-us/rest/api/azure/devops/git/pull%20request%20threads/create?view=azure-devops-rest-6.0#commenttype
Functions:
getAction: gets the action from actions list
parseChanges: gets list of changes and creates multiline summary
validateEnvVars: validates required env variables
formatUrl: formats URL using environment variables
formatDataToSend: creates data to be sent in the request
sendRequest: send the request with the content to devops
main: entrypoint
"""
import logging
import os
import base64
import requests
import terraplanfeed.stdout as stdout

logger = logging.getLogger(__name__)

URLCOMPONENTS = {
"URL": os.getenv("SYSTEM_TEAMFOUNDATIONSERVERURI"),
"PROJ": os.getenv("SYSTEM_TEAMPROJECT"),
"REPOSITORYID": os.getenv("BUILD_REPOSITORY_ID"),
"PULLREQUESTID": os.getenv("SYSTEM_PULLREQUEST_PULLREQUESTID"),
"APIVERSION": "6.0",
}

COMMENTTYPE = {
"CODECHANGE": "codeChange",
"SYSTEM": "system",
"TEXT": "text",
"UNKNOWN": "unknown",
}

COMMENTTHREADSTATUS = {
"ACTIVE": "active",
"BYDESIGN": "byDesign",
"CLOSED": "closed",
"FIXED": "fixed",
"PENDING": "pending",
"UNKNOWN": "unknown",
"WONTFIX": "wontFix",
}

ACTION_SYMBOLS = {
"no-op": ":thumbsup:",
"create": ":sparkles:",
"read": ":open_book:",
"update": ":pencil2:",
"replace": ":warning:",
"delete": ":stop_sign:",
}

HEADER = """
**Terraform Plan changes summary:**
===================================
"""

FOOTER = """
"""


def getAction(actions):
"""
Get action
Args:
actions(list): list of actions
Returns:
action symbol
"""

logger.debug("get action")
if "create" in actions and len(actions) > 1:
return ACTION_SYMBOLS["replace"]
else:
return ACTION_SYMBOLS[actions[0]]


def parseChanges(changes):
"""
Parse changes.
Args:
changes(list): list of resources dict
Returns:
Multiline string with summary of changes
"""

content = ""
logger.debug("parsing changes...")
for c in changes:
action = getAction(c["actions"])
message = "({action}): {name} ({address})".format(
action=action, name=c["name"], address=c["address"]
)
content += message + "\n"
return content


def validateEnvVars():
"""
Validates the contents of the required env variables
Returns:
boolean true or false if they are present
"""

logger.debug("check required env variables")
if URLCOMPONENTS["URL"] is None:
return False
if URLCOMPONENTS["PROJ"] is None:
return False
if URLCOMPONENTS["REPOSITORYID"] is None:
return False
if URLCOMPONENTS["PULLREQUESTID"] is None:
return False
if os.getenv("SYSTEM_ACCESSTOKEN") is None:
return False

return True


def formatUrl():
"""
Formats URL using environment variables.
Returns: url
"""

url = ""
logger.debug("format url")
url = "{url}{proj}/_apis/git/repositories/{repoid}/pullRequests/{prid}/threads?api-version={apiversion}".format(
url=URLCOMPONENTS["URL"],
proj=URLCOMPONENTS["PROJ"],
repoid=URLCOMPONENTS["REPOSITORYID"],
prid=URLCOMPONENTS["PULLREQUESTID"],
apiversion=URLCOMPONENTS["APIVERSION"],
)

return url


def formatDataToSend(content, commentType, statusCode):
"""
Creates JSON content to send to Azure DevOps.
Args:
content(str): multiline string
commentType(str): comment type
statusCode(str): status code
Returns:
dict with the content
"""

formattedContent = HEADER + content + FOOTER
data = {
"comments": [
{
"parentCommentId": 0,
"content": formattedContent,
"commentType": commentType,
}
],
"status": statusCode,
}

return data


def sendRequest(url, data):
"""
Sends request to Azure DevOps API.
Args:
url: URL to send request
data: json content to send to API
Returns:
True or False based on the send request
"""

logger.debug("send request")
logger.debug("url: {}".format(url))
logger.debug("data: {}".format(data))

token = os.getenv("SYSTEM_ACCESSTOKEN")
access_token = ":{}".format(token)

encodedBytes = base64.b64encode(access_token.encode("utf-8"))
encodedStr = str(encodedBytes, "utf-8")

session = requests.Session()
session.headers["Authorization"] = "Basic {}".format(encodedStr)
response = session.post(url, json=data)

return response.status_code


def main(changes):
"""
Handles changes and formats content to send to Azure DevOps API.
Args:
changes(list): list of resources dict
"""

logger.debug("azure devops entrypoint")
if not changes:
content = "No changes " + ACTION_SYMBOLS["no-op"]
commentType = COMMENTTYPE["TEXT"]
statusCode = COMMENTTHREADSTATUS["CLOSED"]
else:
content = parseChanges(changes)
commentType = COMMENTTYPE["CODECHANGE"]
statusCode = COMMENTTHREADSTATUS["ACTIVE"]
data = formatDataToSend(content, commentType, statusCode)
if validateEnvVars():
url = formatUrl()
retcode = sendRequest(url, data)
logger.debug("status code: {}".format(retcode))
else:
stdout.write(content)
9 changes: 6 additions & 3 deletions terraplanfeed/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
import json

from terraplanfeed.terraform import parsePlan
from terraplanfeed.stdout import writeToStdout
import terraplanfeed.stdout as stdout
import terraplanfeed.azuredevops as azuredevops

logger = logging.getLogger(__name__)

Expand All @@ -37,5 +38,7 @@ def terraplanfeed(filename, output):

resources = parsePlan(tf)

if output.lower() == "stdout":
writeToStdout(resources)
if output.lower() == "azuredevops":
azuredevops.main(resources)
else:
stdout.main(resources)
31 changes: 23 additions & 8 deletions terraplanfeed/stdout.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
Functions:
getAction: gets the action from actions list
parseChanges: gets list of changes and creates multiline summary
writeToStdout: writes the summary content to stdout
write: writes the summary content to stdout
main: entrypoint
"""
import logging

Expand All @@ -22,9 +23,8 @@
}

HEADER = """
Summary of changes:
===================
**Terraform Plan changes summary:**
===================================
"""

FOOTER = """
Expand Down Expand Up @@ -71,16 +71,31 @@ def parseChanges(changes):
return content


def writeToStdout(changes):
def write(content):
"""
Writes summary of changes to stdout.
Args:
changes(list): list of resources dict
content(str): multiline string
"""

logger.debug("write to stdout...")
content = parseChanges(changes)
logger.debug("write to stdout")
print(HEADER)
print(content)
print(FOOTER)


def main(changes):
"""
Entrypoint for stdout output driver.
Args:
changes(list): list of resources dict
"""

logger.debug("stdout entrypoint")
if not changes:
content = "No changes"
else:
content = parseChanges(changes)
write(content)

0 comments on commit 3991a93

Please sign in to comment.