Skip to content

Commit

Permalink
Merge pull request #738 from shauneccles/build/refactor_sentry_and_ad…
Browse files Browse the repository at this point in the history
…d_sha

build: refactor sentry init, refactor spec files, wheel workflow, add sha and release to /api/info
  • Loading branch information
shauneccles committed Feb 14, 2024
2 parents a1e8698 + 2a3807c commit c502149
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 80 deletions.
13 changes: 9 additions & 4 deletions .github/workflows/test-build-binaries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -413,10 +413,15 @@ jobs:
- name: Install Poetry
run: |
curl -sSL https://install.python-poetry.org | python3 -
- name: Create prod.env
run: |
echo "IS_RELEASE = true" >> prod.env
- name: Build a binary wheel and sdist
- name: Create and populate ledfx.env
run: |
echo "GITHUB_SHA = \"${{ github.sha }}\"" >> ledfx.env
if [[ "${{ startsWith(github.ref, 'refs/tags/') }}" == "true" ]]; then
echo "IS_RELEASE = true" >> ledfx.env
else
echo "IS_RELEASE = false" >> ledfx.env
fi
- name: Build a binary wheel and sdist
run: |
poetry build
- name: Package the wheel
Expand Down
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ performance_insights.txt

# Debugging task may leave this folder behind if it crashes
debug_config


# spec file build may leave the ledfx.env behind
ledfx.env
# Misc
.devcontainer/ledfx-config.yml
frontend/*.eslintcache
Expand Down
8 changes: 6 additions & 2 deletions ledfx/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import sys
from logging.handlers import RotatingFileHandler

from ledfx.sentry_config import setup_sentry

try:
import psutil

Expand All @@ -29,6 +31,7 @@
currently_frozen,
get_icon_path,
log_packages,
read_ledfx_dotenv,
)


Expand Down Expand Up @@ -216,11 +219,12 @@ def parse_args():

def main():
"""Main entry point allowing external calls"""

args = parse_args()
config_helpers.ensure_config_directory(args.config)
setup_logging(args.loglevel, config_dir=args.config)
config_helpers.load_logger()

read_ledfx_dotenv()
if _LOGGER.isEnabledFor(logging.DEBUG):
log_packages()
check_optional_dependencies()
Expand Down Expand Up @@ -248,7 +252,7 @@ def main():
p.nice(15)

if args.offline_mode is False:
import ledfx.sentry_config # noqa: F401
setup_sentry()

if args.sentry_test:
"""This will crash LedFx and submit a Sentry error if Sentry is configured"""
Expand Down
3 changes: 3 additions & 0 deletions ledfx/api/info.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
import os

from aiohttp import web

Expand All @@ -22,6 +23,8 @@ async def get(self) -> web.Response:
"url": self._ledfx.http.base_url,
"name": "LedFx Controller",
"version": PROJECT_VERSION,
"github_sha": os.getenv("GITHUB_SHA", "unknown"),
"is_release": os.getenv("IS_RELEASE", "false").lower(),
"developer_mode": self._ledfx.config["dev_mode"],
}
return await self.bare_request_success(response)
103 changes: 51 additions & 52 deletions ledfx/sentry_config.py
Original file line number Diff line number Diff line change
@@ -1,63 +1,62 @@
import logging
import os
import sys

import sentry_sdk
from dotenv import load_dotenv
from sentry_sdk.integrations.aiohttp import AioHttpIntegration

from ledfx.consts import PROJECT_VERSION
from ledfx.utils import currently_frozen

_LOGGER = logging.getLogger(__name__)

# Load the prod.env - this does not exist by default, and will thus be false when run from source
extDataDir = os.path.dirname(os.path.realpath(__file__))

if currently_frozen():
extDataDir = sys._MEIPASS
load_dotenv(dotenv_path=os.path.join(extDataDir, "prod.env"))
else:
parent_dir = os.path.dirname(extDataDir)
load_dotenv(dotenv_path=os.path.join(parent_dir, "prod.env"))


is_release = os.getenv("IS_RELEASE", "false").lower()

if is_release == "false":
_LOGGER.debug("Running in development mode.")
sentry_dsn = "https://b192934eebd517c86bf7e9c512b3888a@o482797.ingest.sentry.io/4506350241841152"
# Change sample_rate to 1 to look at http calls, so all rest traffic stats
sample_rate = 0

try:
from subprocess import PIPE, Popen

process = Popen(["git", "rev-parse", "HEAD"], stdout=PIPE)
(commit_hash, err) = process.communicate()
commit_hash = commit_hash[:7].decode("utf-8")
exit_code = process.wait()
# TODO: trap explicit exceptions if it becomes clear what they are
except Exception as e:
commit_hash = os.getenv("GITHUB_SHA", "unknown")
commit_hash = commit_hash[:7].decode("utf-8")
_LOGGER.warning(f"Failed to get git commit hash: {e}")
release = f"ledfx@{PROJECT_VERSION}-{commit_hash}"
else:
_LOGGER.debug("Running in production mode.")
# production / release behaviour due to injection of "prod" or anything really into ENVIRONMENT env variable
sentry_dsn = "https://dc6070345a8dfa1f2f24433d16f7a133@o482797.ingest.sentry.io/4506350233321472"
sample_rate = 0
release = f"ledfx@{PROJECT_VERSION}"

_LOGGER.info("Sentry Configuration:")
_LOGGER.info(f"DSN (first ten): {sentry_dsn[8:18]}")
_LOGGER.info(f"Sample rate: {sample_rate}")
_LOGGER.info(f"LedFx release: {release}")

sentry_sdk.init(
sentry_dsn,
traces_sample_rate=sample_rate,
integrations=[AioHttpIntegration()],
release=release,
)

def setup_sentry():
"""
Set up the Sentry configuration based on the environment variables.
If running in development mode, a development Sentry DSN is used along with a sample rate of 0.
If running in production mode, a production Sentry DSN is used along with a sample rate of 0.
The release version is determined based on the project version and the git commit hash.
"""
is_release = os.getenv("IS_RELEASE", "false").lower()

if is_release == "false":
_LOGGER.debug("Running in development mode.")
sentry_dsn = "https://b192934eebd517c86bf7e9c512b3888a@o482797.ingest.sentry.io/4506350241841152"
# Change sample_rate to 1 to look at http calls, so all rest traffic stats
sample_rate = 0

try:
if currently_frozen():
commit_hash = os.getenv("GITHUB_SHA", "unknown")
else:
from subprocess import PIPE, Popen

process = Popen(["git", "rev-parse", "HEAD"], stdout=PIPE)
(commit_hash, err) = process.communicate()
commit_hash = commit_hash[:7].decode("utf-8")
exit_code = process.wait()
# TODO: trap explicit exceptions if it becomes clear what they are
except Exception as e:
commit_hash = "unknown"
_LOGGER.warning(f"Failed to get git commit hash: {e}")
finally:
commit_hash = commit_hash[:7]
release = f"ledfx@{PROJECT_VERSION}-{commit_hash}"
else:
_LOGGER.debug("Running in production mode.")
# production / release behaviour due to injection of "prod" or anything really into ENVIRONMENT env variable
sentry_dsn = "https://dc6070345a8dfa1f2f24433d16f7a133@o482797.ingest.sentry.io/4506350233321472"
sample_rate = 0
release = f"ledfx@{PROJECT_VERSION}"

_LOGGER.info("Sentry Configuration:")
_LOGGER.info(f"DSN (first ten): {sentry_dsn[8:18]}")
_LOGGER.info(f"Sample rate: {sample_rate}")
_LOGGER.info(f"LedFx release: {release}")

sentry_sdk.init(
sentry_dsn,
traces_sample_rate=sample_rate,
integrations=[AioHttpIntegration()],
release=release,
)
32 changes: 32 additions & 0 deletions ledfx/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import PIL.ImageFont as ImageFont
import requests
import voluptuous as vol
from dotenv import load_dotenv

from ledfx.config import save_config
from ledfx.consts import LEDFX_ASSETS_PATH
Expand Down Expand Up @@ -630,6 +631,37 @@ async def resolve_destination(
raise ValueError(f"Failed to resolve destination {cleaned_dest}")


def read_ledfx_dotenv():
"""
Loads the environment variables from the ledfx.env file.
In normal development, this does not exist and thus does nothing.
During the CI build and release process, the ledfx.env file is created and populated with:
IS_RELEASE = [true|false]
GITHUB_SHA = [commit hash]
It is then packaged in the ledfx folder for distribution.
If the code is running from a frozen executable, it loads the .env file from the executable's directory.
Otherwise, it loads the .env file from the parent directory of the current script.
"""
extDataDir = os.path.dirname(os.path.realpath(__file__))

if currently_frozen():
extDataDir = sys._MEIPASS
path_to_load = os.path.join(extDataDir, "ledfx.env")
else:
parent_dir = os.path.dirname(extDataDir)
path_to_load = os.path.join(parent_dir, "ledfx.env")
if os.path.exists(path_to_load):
load_dotenv(dotenv_path=path_to_load)
_LOGGER.debug(f"Loaded dotenv from {path_to_load}")


def currently_frozen():
"""Checks to see if running in a frozen environment such as pyinstaller created binaries
Args:
Expand Down
30 changes: 21 additions & 9 deletions osx-binary.spec
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,35 @@ spec_root = os.path.abspath(SPECPATH)
venv_root = os.path.abspath(os.path.join(SPECPATH, '..'))
block_cipher = None

# Remove the ledfx.env file if it exists
os.remove("ledfx.env") if os.path.exists("ledfx.env") else None

# if this is a release, create prod.env for the packaged binaries to read from and hide the console
# Get environment variables
github_ref = os.getenv('GITHUB_REF')
github_sha_value = os.getenv('GITHUB_SHA')

# Initialize variables
variables = [f"GITHUB_SHA = {github_sha_value}"]
SHOW_CONSOLE = True

# Check if this is a release
if github_ref and 'refs/tags/' in github_ref:
with open('prod.env', 'w') as file:
file.write('IS_RELEASE = true')
# cleanup github_ref to remove /refs/tags/v and leave just the version
github_ref_cleaned = github_ref.replace('refs/tags/v', '')
assert PROJECT_VERSION == github_ref_cleaned, "Version and Tag do not match - aborting release."
variables.append('IS_RELEASE = true')
SHOW_CONSOLE = False
else:
with open('prod.env', 'w') as file:
file.write('IS_RELEASE = false')
SHOW_CONSOLE = True
variables.append('IS_RELEASE = false')

# Write to ledfx.env file
with open('ledfx.env', 'a') as file:
file.write('\n'.join(variables))

a = Analysis([f'{spec_root}/ledfx/__main__.py'],
pathex=[f'{spec_root}', f'{spec_root}/ledfx'],
binaries=[],
datas=[(f'{spec_root}/ledfx_frontend', 'ledfx_frontend/'), (f'{spec_root}/ledfx/', 'ledfx/'), (f'{spec_root}/ledfx_assets', 'ledfx_assets/'),(f'{spec_root}/ledfx_assets/tray.png','.'), (f'{spec_root}/prod.env','.')],
datas=[(f'{spec_root}/ledfx_frontend', 'ledfx_frontend/'), (f'{spec_root}/ledfx/', 'ledfx/'), (f'{spec_root}/ledfx_assets', 'ledfx_assets/'),(f'{spec_root}/ledfx_assets/tray.png','.'), (f'{spec_root}/ledfx.env','.')],
hiddenimports=hiddenimports,
hookspath=[f'{venv_root}/lib/site-packages/pyupdater/hooks'],
runtime_hooks=[],
Expand Down Expand Up @@ -69,5 +81,5 @@ app = BUNDLE(exe,
'com.apple.security.device.audio-input': True,
'com.apple.security.device.microphone': True,
})
# Cleanup prod.env
os.remove("prod.env")
# Cleanup ledfx.env
os.remove("ledfx.env")
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ include = [
"AUTHORS.rst",
"CHANGELOG.rst",
"LICENSE.txt",
"prod.env"
"ledfx.env"
]

[tool.poetry.scripts]
Expand Down
34 changes: 24 additions & 10 deletions windows-binary.spec
Original file line number Diff line number Diff line change
@@ -1,26 +1,40 @@
# -*- mode: python ; coding: utf-8 -*-
import os
from hiddenimports import hiddenimports
from ledfx.consts import PROJECT_VERSION
spec_root = os.path.abspath(SPECPATH)

venv_root = os.path.abspath(os.path.join(SPECPATH, '..'))
block_cipher = None
print(venv_root)
# Remove the ledfx.env file if it exists
os.remove("ledfx.env") if os.path.exists("ledfx.env") else None


# if this is a release, create prod.env for the packaged binaries to read from and hide the console
# Get environment variables
github_ref = os.getenv('GITHUB_REF')
github_sha_value = os.getenv('GITHUB_SHA')

# Initialize variables
variables = [f"GITHUB_SHA = {github_sha_value}"]
SHOW_CONSOLE = True

# Check if this is a release
if github_ref and 'refs/tags/' in github_ref:
with open('prod.env', 'w') as file:
file.write('IS_RELEASE = true')
# cleanup github_ref to remove /refs/tags/v and leave just the version
github_ref_cleaned = github_ref.replace('refs/tags/v', '')
assert PROJECT_VERSION == github_ref_cleaned, "Version and Tag do not match - aborting release."
variables.append('IS_RELEASE = true')
SHOW_CONSOLE = False
else:
with open('prod.env', 'w') as file:
file.write('IS_RELEASE = false')
SHOW_CONSOLE = True
variables.append('IS_RELEASE = false')

# Write to ledfx.env file
with open('ledfx.env', 'a') as file:
file.write('\n'.join(variables))
a = Analysis([f'{spec_root}\\ledfx\\__main__.py'],
pathex=[f'{spec_root}', f'{spec_root}\\ledfx'],
binaries=[],
datas=[(f'{spec_root}/ledfx_frontend', 'ledfx_frontend/'), (f'{spec_root}/ledfx/', 'ledfx/'), (f'{spec_root}/ledfx_assets', 'ledfx_assets/'),(f'{spec_root}/ledfx_assets/tray.png','.'), (f'{spec_root}/prod.env','.')],
datas=[(f'{spec_root}/ledfx_frontend', 'ledfx_frontend/'), (f'{spec_root}/ledfx/', 'ledfx/'), (f'{spec_root}/ledfx_assets', 'ledfx_assets/'),(f'{spec_root}/ledfx_assets/tray.png','.'), (f'{spec_root}/ledfx.env','.')],
hiddenimports=hiddenimports,
hookspath=[f'{venv_root}\\lib\\site-packages\\pyupdater\\hooks'],
runtime_hooks=[],
Expand Down Expand Up @@ -51,5 +65,5 @@ coll = COLLECT(exe,
upx_exclude=[],
name='LedFx')

# Cleanup prod.env
os.remove("prod.env")
# Cleanup ledfx.env
os.remove("ledfx.env")

0 comments on commit c502149

Please sign in to comment.