Skip to content

Commit

Permalink
Add nightly deployment
Browse files Browse the repository at this point in the history
Automates deploying nightly builds to the dropbox shared folder and to
each of Wrye Bash's nexus pages.

Dropbox:
 - Uses dropbox's cli oauth to allow access to the shared folder;
 - The oauth process above can be overridden by providing your own
   access token;
 - Will deploy to the root of the shared folder - if previous builds are
   present it will delete them before uploading the new ones;
 - Test/Branch builds are supported by deploying to a subfolder via the
   '--branch' flag.

Nexus:
 - Requires Google Chrome installed and an active nexus session in
   either Chrome, Firefox or Safari;
 - The appropriate chromedriver for the Chrome version is automatically
   downloaded;
 - The required nexus auth cookies ('member_id', 'pass_hash', and 'sid')
   are automatically retrieved for one of the above browsers;
 - Both chromedriver version and auth cookies can be overridden;
 - Selenium will run Chrome headless while deploying to prevent
   accidental user input;
 - If previous builds are present in the Updates section that match the
   current regex they will be deleted and replaced by the newer files.

A log is kept of all verbose output (sensitive information is not
logged). The path to this log can be changed via '--logfile'.

The authentication credentials are stored in plain text to ease
multiple deployments - this can be disabled via '--no-config'.

For testing purposes, a dry run can be executed via '--dry-run'.

The folder containing distributables to deploy can be changed via
'--dist-folder'.

It is recommended to always use 'deploy.py' to deploy - disabling
deployment to either target can be via '--no-dropbox' or '--no-nexus'.
  • Loading branch information
GandaG committed Sep 26, 2019
1 parent c15003d commit 67eab34
Show file tree
Hide file tree
Showing 6 changed files with 675 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Expand Up @@ -30,6 +30,9 @@ scripts/dist/*.7z
# Local NSIS
scripts/build/nsis/

# Deploy
scripts/deploy/

# translation files
Mopy/bash/l10n/*.mo
Mopy/bash/l10n/*NEW.txt
Expand Down
3 changes: 3 additions & 0 deletions requirements.txt
Expand Up @@ -5,4 +5,7 @@ https://github.com/wrye-bash/dev-tools/raw/master/wheels/wxPython-3.0.2.0-cp27-c
scandir>=1.9.0
# Compile/Build-time
pygit2>=0.28.0
dropbox>=9.3.0
selenium>=3.141.0
browsercookie>=0.6.2
https://github.com/wrye-bash/dev-tools/raw/master/wheels/py2exe-0.6.9-cp27-cp27m-win32.whl
80 changes: 80 additions & 0 deletions scripts/deploy.py
@@ -0,0 +1,80 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-

# GPL License and Copyright Notice ============================================
# This file is part of Wrye Bash.
#
# Wrye Bash is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# Wrye Bash is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Wrye Bash; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# Wrye Bash copyright (C) 2005-2009 Wrye, 2010-2019 Wrye Bash Team
# https://github.com/wrye-bash
#
# =============================================================================

"""
Deploy nightly builds.
To deploy to dropbox you need a Dropbox account and access to the wrye bash
shared folder in your dropbox. Optionally, you can override the regular oauth
process with your own API access token via '--access-token'. Deploying test
builds to a subfolder is also supported with '--branch'.
To deploy to nexus you need to have Google Chrome/Chromium installed and be
logged in to nexus in a browser [Supported: Chrome, Firefox, Safari]. The
appropriate selenium driver and the needed auth cookies are automatically
retrieved. If there are issues with this process, you can override the
chromedriver version with '--driver-version' and the auth cookie values with
'--member-id', '--pass-hash' and '--sid'.
Unless '--no-config' is supplied, all values are saved to a
configuration file at './deploy_config.json'. Values are
stored as a dictionary with the format (keys in lowercase):
'%ARGUMENT%': '%VALUE%'
"""

import argparse

import deploy_dropbox
import deploy_nexus
import utils

if __name__ == "__main__":
parser = argparse.ArgumentParser(
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
)
utils.setup_deploy_parser(parser)
parser.add_argument(
"--no-dropbox",
action="store_false",
dest="dropbox",
help="Do not deploy to dropbox.",
)
parser.add_argument(
"--no-nexus",
action="store_false",
dest="nexus",
help="Do not deploy to nexusmods.",
)
dropbox_parser = parser.add_argument_group("dropbox arguments")
deploy_dropbox.setup_parser(dropbox_parser)
nexus_parser = parser.add_argument_group("nexus arguments")
deploy_nexus.setup_parser(nexus_parser)
args = parser.parse_args()
open(args.logfile, "w").close()
if args.dropbox:
deploy_dropbox.main(args)
print
if args.nexus:
deploy_nexus.main(args)
125 changes: 125 additions & 0 deletions scripts/deploy_dropbox.py
@@ -0,0 +1,125 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-

# GPL License and Copyright Notice ============================================
# This file is part of Wrye Bash.
#
# Wrye Bash is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# Wrye Bash is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Wrye Bash; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# Wrye Bash copyright (C) 2005-2009 Wrye, 2010-2019 Wrye Bash Team
# https://github.com/wrye-bash
#
# =============================================================================

import argparse
import json
import logging
import os
import re

import dropbox

import utils

LOGGER = logging.getLogger(__name__)

# constants
SHARED_FOLDER_ID = "4796182912"
FILE_REGEX = r"Wrye Bash \d{3,}\.\d{12,12} - (Installer.exe|Python Source.7z|Standalone Executable.7z)"
COMPILED = re.compile(FILE_REGEX)


def setup_parser(parser):
parser.add_argument(
"-t",
"--access-token",
default=argparse.SUPPRESS,
help="The dropbox API access token.\n"
" To get your own access token\n"
" go to https://www.dropbox.com/developers/apps\n"
" register an app and generate your token.",
)
parser.add_argument(
"-b",
"--branch",
default="",
help="Upload a specific branch.\n"
" Will upload to a separate folder\n"
" within the shared folder.",
)


def remove_files(dbx, path, dry_run=False):
# get all files in folder
files = []
for entry in dbx.files_list_folder(path).entries:
if isinstance(entry, dropbox.files.FileMetadata):
files.append(entry.name)
LOGGER.debug("Found '{}' under shared folder.".format(entry.name))
# delete the previous nightly files
filtered = filter(COMPILED.match, files)
for fname in filtered:
fpath = path + "/" + fname
if dry_run:
LOGGER.info("Would remove '{}'.".format(fpath))
continue
LOGGER.info("Removing '{}'...".format(fpath))
dbx.files_delete_v2(fpath)


def upload_file(dbx, fpath, folder_path, dry_run=False):
fname = os.path.basename(fpath)
LOGGER.debug("Found '{}' under distributable folder.".format(fname))
if dry_run:
LOGGER.info("Would upload '{}'.".format(os.path.relpath(fpath, os.getcwd())))
return
LOGGER.info("Uploading '{}'...".format(os.path.relpath(fpath, os.getcwd())))
with open(fpath, "rb") as fopen:
upload_path = folder_path + "/" + fname
dbx.files_upload(fopen.read(), upload_path)


def main(args):
utils.setup_log(LOGGER, verbosity=args.verbosity, logfile=args.logfile)
creds = utils.parse_deploy_credentials(args, ["access_token"], args.save_config)
# setup dropbox instance
dbx = dropbox.Dropbox(creds["access_token"])
shared_folder_path = dbx.sharing_get_folder_metadata(SHARED_FOLDER_ID).path_lower
LOGGER.debug("Found shared folder path at '{}'.".format(shared_folder_path))
# create folder inside shared folder if needed for branch nightly
if args.branch:
shared_folder_path += "/" + args.branch
try:
dbx.files_create_folder_v2(shared_folder_path)
except dropbox.exceptions.ApiError:
pass
LOGGER.debug(
"Using branch folder '{}' at '{}'.".format(args.branch, shared_folder_path)
)
remove_files(dbx, shared_folder_path, args.dry_run)
for fname in os.listdir(args.dist_folder):
fpath = os.path.join(args.dist_folder, fname)
if not os.path.isfile(fpath):
continue
upload_file(dbx, fpath, shared_folder_path, args.dry_run)


if __name__ == "__main__":
argparser = argparse.ArgumentParser()
utils.setup_deploy_parser(argparser)
setup_parser(argparser)
parsed_args = argparser.parse_args()
open(parsed_args.logfile, "w").close()
main(parsed_args)

9 comments on commit 67eab34

@GandaG
Copy link
Member Author

@GandaG GandaG commented on 67eab34 Sep 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wrye-bash/bashers Requesting low-priority review on this.

If this is accepted, a few things so I won't forget:

  • Utumno needs to make an "official" wrye bash dropbox app - I have a temporary one set up - and then change the current client id /secret to that one;
  • Is the cookie auth too invasive? It's the only was I could find to make it auto login without a huge hassle from the deployer;
  • While this only deploys nightly builds, deploying production to nexus can be done in theory but the testing is a little dangerous and I didn't want to be too adventurous before getting this accepted.

I remembered I know a bit of javascript and fixed the "no-deleting" issue that selenium has. Keeping a link to the previous discussion at d38db40

@Utumno
Copy link
Member

@Utumno Utumno commented on 67eab34 Sep 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @GandaG how do I make a dropbox app?

@GandaG
Copy link
Member Author

@GandaG GandaG commented on 67eab34 Sep 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Utumno it's pretty simple. Once you're done, all that's left is changing the client id and secret on the script.

@Utumno
Copy link
Member

@Utumno Utumno commented on 67eab34 Sep 27, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well I kinda made an app but not sure about security - should we have the cleintid and secret on the script? I guess not - so probably shouldn't be read from an ini file? On the other hand, if this is already working do we need an app? Or the shared folder id would be enough and then we don't need to worry about security (as only users with access to the shared folder and dropbox installed would be able to post files in there IIUC)

Or should I add you and Infernio to the app
dbapp

Would be nice to add some docs to the scripts (that could be used as the argparser help too)

@GandaG
Copy link
Member Author

@GandaG GandaG commented on 67eab34 Sep 28, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not the best with internet-y stuff but I tried to do some research on this back then.

You'll always need a dropbox app since you need to authenticate with a dropbox endpoint to prove it's you.

As for the id+secret, I'd say it mostly depends on whether you can have permissions on the app folder - I'll test that tomorrow. If you can set them, then you can clear us three for modifications and even if someone else were to use the app (which only allows connecting to that specific folder) wouldn't be able to do anything - no problem exposing id and secret.

If you can't though, that's a different story. If we're going with an 'App Folder' type app like you made then exposing the secret is super no bueno and we definitely need to control the app secret and pin it somewhere that's dev-only.

I also dont think you can whitelist users to the app, although that would solve the issue.

Sure about docs, didn't write anything there because I wasn't sure what to write about :P suggestions?

@Utumno
Copy link
Member

@Utumno Utumno commented on 67eab34 Sep 28, 2019 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@GandaG
Copy link
Member Author

@GandaG GandaG commented on 67eab34 Sep 28, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a quick google it seems sharing app folders is not allowed :/

you need the Dropbox app installed

You don't! :)

having permissions on the common folder

Well, that's true :P

but if we can get away without the app and just use the folder as we do now then maybe that's good enough?

Ok I think I know where you're going with this - you want to keep dragging the files to the local dropbox folder, don't you?! I'll let you know that automating this increases our efficiency by 93% by reducing finger stress from all the clicking and dragging and instead transforming it into the much more manageable cardiac stress from those 'oh my God please work this time' thoughts. Not to mention all the headaches it saved us with beta 4, we were literally uploading files faster than nexus could process them, and with my crappy internet to boot!

Not to mention that CI is on the horizon! And even though you denigrate its merits and drag its name through the mud, it is still a vital member of our future! You push for further infrastructure changes with no thought of their backbone. With tests fully fleshed out, who will run them after every commit? Us! Manually? Madness!

And we still have many changes to go through. Python 3, a proper virtualenv with package version locks, a packaging software that is not 10 years old. Every change brings us new opportunities to expand, maybe even to linux someday! But without proper infrastructure these changes will be costlier and costlier as time goes on and we sink into the void of manual labor.

You may look at this and see just an extra step to uploading files to dropbox, but I see the start of a bright future where the Wrye Bash devs only need to focus on code - a pre-commit hook ensures that all tests pass and backend is solid, a linter and a formatter ensure the code is up-to-standard; a CI server builds and tests our code in multiple OSs and at the end of a release cycle fully deploys our application to all necessary locations. And throughout all this, the humble dev is already picturing the heights that could be achieved in the next release.

Denying our devs the chance for this small improvement in their lives is a fair way to win the battle, but lose the war.

P.S.: I may have been watching too much UK parliament this week. Maybe.

@Utumno
Copy link
Member

@Utumno Utumno commented on 67eab34 Sep 28, 2019 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@GandaG
Copy link
Member Author

@GandaG GandaG commented on 67eab34 Sep 28, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you like it? :P I felt inspired ahah

I was just wondering if we need the app or the script as it is now (with that common folder) could work alright

Well, the script DOES work right now, BUT you brought up a very valid point before that I, uh, totally had thought of before, obviously.

The app I registered is a 'Full Dropbox' so we could use/test via the existing shared folder. Since the secret is committed someone else could make an app using my registered app - while they wouldn't be able to modify the shared folder they could do some illegal stuff and put my account in trouble. I'm monitoring the number of registered users on the app console but still, I'd like a solution for this :D

We could pin the secret/id in the discord dev channel until we find a better place for it, I'll work on some way to locally store them.

RE: docs - I already documented the shared folder access here.

Aside from all this, there's one thing I've never been happy with, the "credentials" parsing. Any suggestions on this?

Please sign in to comment.