def render(self, session, logger, dbuser, sandbox, start, get, comments, **arguments): if not dbuser: raise AuthorizationException("Cannot create a sandbox without an " "authenticated connection.") sandbox = self.force_my_sandbox(session, logger, dbuser, sandbox) # See `git check-ref-format --help` for naming restrictions. # We want to layer a few extra restrictions on top of that... validate_template_name("--sandbox", sandbox) Branch.get_unique(session, sandbox, preclude=True) # Check that the user has cleared up a directory of the same # name; if this is not the case the branch may be created (in git) # and added to the database - however CommandGet will fail roleing # back the database leaving the branch created in git templatesdir = self.config.get("broker", "templatesdir") sandboxdir = os.path.join(templatesdir, dbuser.name, sandbox) if os.path.exists(sandboxdir): raise ArgumentError("Sandbox directory %s already exists; " "cannot create branch." % sandboxdir) if not start: start = self.config.get("broker", "default_domain_start") dbstart = Branch.get_unique(session, start, compel=True) kingdir = self.config.get("broker", "kingdir") base_commit = run_git(["show-ref", "--hash", "refs/heads/" + dbstart.name], logger=logger, path=kingdir) compiler = self.config.get("panc", "pan_compiler") dbsandbox = Sandbox(name=sandbox, owner=dbuser, compiler=compiler, base_commit=base_commit, comments=comments) session.add(dbsandbox) session.flush() # Currently this will fail if the branch already exists... # That seems like the right behavior. It's an internal # consistency issue that would need to be addressed explicitly. run_git(["branch", sandbox, dbstart.name], logger=logger, path=kingdir) # If we arrive there the above "git branch" command has succeeded; # therefore we should comit the changes to the database. If this is # not done, and CommandGet fails (see dir check above), then the # git branch will be created but the database changes roled back. session.commit() if get == False: # The client knows to interpret an empty response as no action. return [] return CommandGet.render(self, session=session, logger=logger, dbuser=dbuser, sandbox=sandbox)
def render(self, session, logger, branch, sandbox, bundle, sync, rebase, **arguments): # Most of the logic here is duplicated in deploy if branch: sandbox = branch dbsandbox = Sandbox.get_unique(session, sandbox, compel=True) (handle, filename) = mkstemp() contents = b64decode(bundle) write_file(filename, contents, logger=logger) if sync and not dbsandbox.is_sync_valid and dbsandbox.trackers: # FIXME: Maybe raise an ArgumentError and request that the # command run with --nosync? Maybe provide a --validate flag? # For now, we just auto-flip anyway (below) making the point moot. pass if not dbsandbox.is_sync_valid: dbsandbox.is_sync_valid = True if rebase and dbsandbox.trackers: raise ArgumentError( "{0} has trackers, rebasing is not allowed.".format(dbsandbox)) kingdir = self.config.get("broker", "kingdir") rundir = self.config.get("broker", "rundir") tempdir = mkdtemp(prefix="publish_", suffix="_%s" % dbsandbox.name, dir=rundir) try: run_git([ "clone", "--shared", "--branch", dbsandbox.name, kingdir, dbsandbox.name ], path=tempdir, logger=logger) temprepo = os.path.join(tempdir, dbsandbox.name) run_git(["bundle", "verify", filename], path=temprepo, logger=logger) ref = "HEAD:%s" % (dbsandbox.name) command = ["pull", filename, ref] if rebase: command.append("--force") run_git(command, path=temprepo, logger=logger, loglevel=CLIENT_INFO) # FIXME: Run tests before pushing back to template-king if rebase: target_ref = "+" + dbsandbox.name else: target_ref = dbsandbox.name run_git(["push", "origin", target_ref], path=temprepo, logger=logger) except ProcessException, e: raise ArgumentError("\n%s%s" % (e.out, e.err))
def sandbox_has_latest(self, config, sandboxdir): domainsdir = config.get("broker", "domainsdir") prod_domain = config.get("broker", "default_domain_start") proddir = os.path.join(domainsdir, prod_domain) try: prod_commit = run_git(["rev-list", "-n", "1", "HEAD"], path=proddir, logger=self.logger).strip() except ProcessException: prod_commit = "" if not prod_commit: raise InternalError("Error finding top commit for %s" % prod_domain) filterre = re.compile("^" + prod_commit + "$") try: found_latest = run_git(["rev-list", "HEAD"], path=sandboxdir, logger=self.logger, filterre=filterre) except ProcessException: self.logger.warn("Failed to run git command in sandbox %s." % sandboxdir) found_latest = "" return bool(found_latest)
def render(self, session, logger, dbuser, sandbox, start, get, comments, **arguments): if not dbuser: raise AuthorizationException("Cannot create a sandbox without an " "authenticated connection.") sandbox = self.force_my_sandbox(session, logger, dbuser, sandbox) # See `git check-ref-format --help` for naming restrictions. # We want to layer a few extra restrictions on top of that... valid = re.compile('^[a-zA-Z0-9_.-]+$') if (not valid.match(sandbox)): raise ArgumentError("sandbox name '%s' is not valid" % sandbox) Branch.get_unique(session, sandbox, preclude=True) if not start: start = self.config.get("broker", "default_domain_start") dbstart = Branch.get_unique(session, start, compel=True) kingdir = self.config.get("broker", "kingdir") base_commit = run_git(["show-ref", "--hash", "refs/heads/" + dbstart.name], logger=logger, path=kingdir) compiler = self.config.get("panc", "pan_compiler") dbsandbox = Sandbox(name=sandbox, owner=dbuser, compiler=compiler, base_commit=base_commit, comments=comments) session.add(dbsandbox) session.flush() # Currently this will fail if the branch already exists... # That seems like the right behavior. It's an internal # consistency issue that would need to be addressed explicitly. run_git(["branch", sandbox, dbstart.name], logger=logger, path=kingdir) if get == False: # The client knows to interpret an empty response as no action. return [] return CommandGet.render(self, session=session, logger=logger, dbuser=dbuser, sandbox=sandbox)
def remove_branch(config, logger, dbbranch): session = object_session(dbbranch) deps = get_branch_dependencies(dbbranch) if deps: raise ArgumentError("\n".join(deps)) session.delete(dbbranch) domain = TemplateDomain(dbbranch, logger=logger) # Can this fail? Is recovery needed? with CompileKey(domain=dbbranch.name, logger=logger): for dir in domain.directories(): remove_dir(dir, logger=logger) kingdir = config.get("broker", "kingdir") try: run_git(["branch", "-D", dbbranch.name], path=kingdir, logger=logger) except ProcessException, e: logger.warning("Error removing branch %s from template-king, " "proceeding anyway: %s", dbbranch.name, e)
def remove_branch(config, logger, dbbranch): session = object_session(dbbranch) deps = get_branch_dependencies(dbbranch) if deps: raise ArgumentError("\n".join(deps)) session.delete(dbbranch) domain = TemplateDomain(dbbranch, logger=logger) # Can this fail? Is recovery needed? with CompileKey(domain=dbbranch.name, logger=logger): for dir in domain.directories(): remove_dir(dir, logger=logger) kingdir = config.get("broker", "kingdir") try: run_git(["branch", "-D", dbbranch.name], path=kingdir, logger=logger) except ProcessException, e: logger.warning( "Error removing branch %s from template-king, " "proceeding anyway: %s", dbbranch.name, e)
def sandbox_has_latest(self, config, sandboxdir): domainsdir = config.get('broker', 'domainsdir') prod_domain = config.get('broker', 'default_domain_start') proddir = os.path.join(domainsdir, prod_domain) try: prod_commit = run_git(['rev-list', '-n', '1', 'HEAD'], path=proddir, logger=self.logger).strip() except ProcessException: prod_commit = '' if not prod_commit: raise InternalError("Error finding top commit for %s" % prod_domain) filterre = re.compile('^' + prod_commit + '$') try: found_latest = run_git(['rev-list', 'HEAD'], path=sandboxdir, logger=self.logger, filterre=filterre) except ProcessException: self.logger.warn("Failed to run git command in sandbox %s." % sandboxdir) found_latest = '' return bool(found_latest)
def render(self, session, logger, branch, sandbox, bundle, sync, rebase, **arguments): # Most of the logic here is duplicated in deploy if branch: sandbox = branch dbsandbox = Sandbox.get_unique(session, sandbox, compel=True) (handle, filename) = mkstemp() contents = b64decode(bundle) write_file(filename, contents, logger=logger) if sync and not dbsandbox.is_sync_valid and dbsandbox.trackers: # FIXME: Maybe raise an ArgumentError and request that the # command run with --nosync? Maybe provide a --validate flag? # For now, we just auto-flip anyway (below) making the point moot. pass if not dbsandbox.is_sync_valid: dbsandbox.is_sync_valid = True if rebase and dbsandbox.trackers: raise ArgumentError("{0} has trackers, rebasing is not allowed." .format(dbsandbox)) kingdir = self.config.get("broker", "kingdir") rundir = self.config.get("broker", "rundir") tempdir = mkdtemp(prefix="publish_", suffix="_%s" % dbsandbox.name, dir=rundir) try: run_git(["clone", "--shared", "--branch", dbsandbox.name, kingdir, dbsandbox.name], path=tempdir, logger=logger) temprepo = os.path.join(tempdir, dbsandbox.name) run_git(["bundle", "verify", filename], path=temprepo, logger=logger) ref = "HEAD:%s" % (dbsandbox.name) command = ["pull", filename, ref] if rebase: command.append("--force") run_git(command, path=temprepo, logger=logger, loglevel=CLIENT_INFO) # FIXME: Run tests before pushing back to template-king if rebase: target_ref = "+" + dbsandbox.name else: target_ref = dbsandbox.name run_git(["push", "origin", target_ref], path=temprepo, logger=logger) except ProcessException, e: raise ArgumentError("\n%s%s" % (e.out, e.err))
def render(self, session, logger, domain, ref, lastsync, **arguments): dbdomain = Domain.get_unique(session, domain, compel=True) if not dbdomain.tracked_branch: # Could check dbdomain.trackers and rollback all of them... raise ArgumentError("rollback requires a tracking domain") if lastsync: if not dbdomain.rollback_commit: raise ArgumentError("domain %s does not have a rollback " "commit saved, please specify one " "explicitly." % dbdomain.name) ref = dbdomain.rollback_commit if not ref: raise ArgumentError("Commit reference to rollback to required.") kingdir = self.config.get("broker", "kingdir") domaindir = os.path.join(self.config.get("broker", "domainsdir"), dbdomain.name) out = run_git(["branch", "--contains", ref], logger=logger, path=kingdir) if not re.search(r'\b%s\b' % dbdomain.tracked_branch.name, out): # There's no real technical reason why this needs to be # true. It just seems like a good sanity check. raise ArgumentError("Cannot roll back to commit: " "branch %s does not contain %s" % (dbdomain.tracked_branch.name, ref)) dbdomain.tracked_branch.is_sync_valid = False session.add(dbdomain.tracked_branch) dbdomain.rollback_commit = None session.add(dbdomain) key = CompileKey(domain=dbdomain.name, logger=logger) try: lock_queue.acquire(key) run_git(["push", ".", "+%s:%s" % (ref, dbdomain.name)], path=kingdir, logger=logger) # Duplicated this logic from aquilon.worker.processes.sync_domain() run_git(["fetch"], path=domaindir, logger=logger) run_git(["reset", "--hard", "origin/%s" % dbdomain.name], path=domaindir, logger=logger) except ProcessException, e: raise ArgumentError( "Problem encountered updating templates for " "domain %s: %s", dbdomain.name, e)
def render(self, session, logger, domain, ref, lastsync, **arguments): dbdomain = Domain.get_unique(session, domain, compel=True) if not dbdomain.tracked_branch: # Could check dbdomain.trackers and rollback all of them... raise ArgumentError("rollback requires a tracking domain") if lastsync: if not dbdomain.rollback_commit: raise ArgumentError("domain %s does not have a rollback " "commit saved, please specify one " "explicitly." % dbdomain.name) ref = dbdomain.rollback_commit if not ref: raise ArgumentError("Commit reference to rollback to required.") kingdir = self.config.get("broker", "kingdir") domaindir = os.path.join(self.config.get("broker", "domainsdir"), dbdomain.name) out = run_git(["branch", "--contains", ref], logger=logger, path=kingdir) if not re.search(r'\b%s\b' % dbdomain.tracked_branch.name, out): # There's no real technical reason why this needs to be # true. It just seems like a good sanity check. raise ArgumentError("Cannot roll back to commit: " "branch %s does not contain %s" % (dbdomain.tracked_branch.name, ref)) dbdomain.tracked_branch.is_sync_valid = False session.add(dbdomain.tracked_branch) dbdomain.rollback_commit = None session.add(dbdomain) key = CompileKey(domain=dbdomain.name, logger=logger) try: lock_queue.acquire(key) run_git(["push", ".", "+%s:%s" % (ref, dbdomain.name)], path=kingdir, logger=logger) # Duplicated this logic from aquilon.worker.processes.sync_domain() run_git(["fetch"], path=domaindir, logger=logger) run_git(["reset", "--hard", "origin/%s" % dbdomain.name], path=domaindir, logger=logger) except ProcessException, e: raise ArgumentError("Problem encountered updating templates for " "domain %s: %s", dbdomain.name, e)
def main(): print "Calculating sandbox base commits. This may take around 10 minutes." logging.basicConfig(level=logging.WARNING) kingdir = config.get("broker", "kingdir") domains = session.query(Domain).all() # Define preference order when multiple domains have the same commits. # This is just cosmetics, but makes it easier to verify the output. for idx, domain in enumerate(("prod", "qa", "secure-aquilon-prod", "secure-aquilon-qa")): dbdom = Domain.get_unique(session, domain, compel=True) domains.remove(dbdom) domains.insert(idx, dbdom) base_commits = {} q = session.query(Sandbox) q = q.order_by('name') # The base_commit column does not exist yet... q = q.options(defer("base_commit")) for sandbox in q: base_domain = None base_commit = None min_ahead = None commits = run_git(["rev-list", "refs/heads/" + sandbox.name], path=kingdir).split("\n") for domain in domains: merge_base = run_git(["merge-base", "refs/heads/" + sandbox.name, "refs/heads/" + domain.name], path=kingdir).strip() # Number of commits since branching from the given domain ahead = commits.index(merge_base) if base_domain is None or ahead < min_ahead: base_domain = domain base_commit = merge_base min_ahead = ahead if min_ahead == 0: break print "{0: <40}: {1.name} (ahead {2})".format(sandbox, base_domain, min_ahead) base_commits[sandbox.name] = base_commit session.expunge_all() try: if session.bind.dialect.name == 'oracle': query = text(""" ALTER TABLE sandbox ADD base_commit VARCHAR2(40 CHAR) """) elif session.bind.dialect.name == 'postgresql': query = text(""" ALTER TABLE sandbox ADD base_commit CHARACTER VARYING (40) """) print "\nExecuting: %s" % query session.execute(query) session.commit() except DatabaseError: # Allow the script to be re-run by not failing if the column already # exists. If the column does not exist, then trying to update it will # fail anyway. print """ WARNING: Adding the sandbox.base_commit column has failed. If you're running this script for the second time, then that's likely OK, otherwise you should verify and correct the schema manually. """ session.rollback() for sandbox in q: sandbox.base_commit = base_commits[sandbox.name] session.commit() try: if session.bind.dialect.name == 'oracle': query = text(""" ALTER TABLE sandbox MODIFY (base_commit VARCHAR2(40 CHAR) CONSTRAINT sandbox_base_commit_nn NOT NULL) """) elif session.bind.dialect.name == 'postgresql': query = text(""" ALTER TABLE sandbox ALTER COLUMN base_commit SET NOT NULL """) print "\nExecuting: %s" % query session.execute(query) session.commit() except DatabaseError: print """ WARNING: Enabling the NOT NULL constraint for sandbox.base_commit column has failed. If you're running this script for the second time, then that's likely OK, otherwise you should verify and correct the schema manually. """ session.rollback()
def validate_branch_commits(dbsource, dbsource_author, dbtarget, dbtarget_author, logger, config): domainsdir = config.get('broker', 'domainsdir') if isinstance(dbsource, Sandbox): authored_sandbox = AuthoredSandbox(dbsource, dbsource_author) source_path = authored_sandbox.path else: source_path = os.path.join(domainsdir, dbsource.name) if isinstance(dbtarget, Sandbox): authored_sandbox = AuthoredSandbox(dbtarget, dbtarget_author) target_path = authored_sandbox.path else: target_path = os.path.join(domainsdir, dbtarget.name) # check if dbsource has anything uncommitted git_status = run_git(["status", "--porcelain"], path=source_path, logger=logger) if git_status: raise ArgumentError("The source {0:l} contains uncommitted files." .format(dbsource)) # get latest source commit bit dbsource_commit = run_git(['rev-list', '--max-count=1', 'HEAD'], path=source_path, logger=logger) dbsource_commit = dbsource_commit.rstrip() if not dbsource_commit: # pragma: no cover raise ArgumentError("Unable to retrieve the git commit history from " "source branch {0:l}.".format(dbsource)) # make sure all commits in the source have been published. # we can check the latest commit bit from the source in template-king # any results returned will mean that all commits has been published kingdir = config.get("broker", "kingdir") try: found = run_git(['cat-file', '-t', dbsource_commit], path=kingdir, logger=logger) found = found.strip() except ProcessException as pe: if pe.code != 128: raise else: found = None if found != 'commit': raise ArgumentError("The source {0:l} latest commit has not been " "published to template-king yet.".format(dbsource)) # check if target branch has the latest source commit try: filterre = re.compile('^' + dbsource_commit + '$') found = run_git(['rev-list', 'HEAD'], filterre=filterre, path=target_path, logger=logger) except ProcessException as pe: if pe.code != 128: raise else: found = None if not found: raise ArgumentError("The target {0:l} does not contain the latest " "commit from source {1:l}.".format(dbtarget, dbsource))
def render(self, session, logger, source, target, sync, dryrun, comments, justification, user, requestid, **arguments): # Most of the logic here is duplicated in publish dbsource = Branch.get_unique(session, source, compel=True) # The target has to be a non-tracking domain dbtarget = Domain.get_unique(session, target, compel=True) if sync and isinstance(dbtarget.tracked_branch, Domain) \ and dbtarget.tracked_branch.autosync and dbtarget.autosync: # The user probably meant to deploy to the tracked branch, # but only do so if all the relevant autosync flags are # positive. logger.warning("Deploying to tracked branch %s and then will " "auto-sync %s" % ( dbtarget.tracked_branch.name, dbtarget.name)) dbtarget = dbtarget.tracked_branch elif dbtarget.tracked_branch: raise ArgumentError("Cannot deploy to tracking domain %s. " "Did you mean domain %s?" % (dbtarget.name, dbtarget.tracked_branch.name)) if sync and not dbtarget.is_sync_valid and dbtarget.trackers: # FIXME: Maybe raise an ArgumentError and request that the # command run with --nosync? Maybe provide a --validate flag? # For now, just auto-flip (below). pass if not dbtarget.is_sync_valid: dbtarget.is_sync_valid = True if dbtarget.requires_change_manager: if not justification: raise AuthorizationException( "{0} is under change management control. Please specify " "--justification.".format(dbtarget)) validate_justification(user, justification) if isinstance(dbsource, Sandbox): domainsdir = self.config.get('broker', 'domainsdir') targetdir = os.path.join(domainsdir, dbtarget.name) filterre = re.compile('^' + dbsource.base_commit + '$') found = run_git(['rev-list', 'HEAD'], path=targetdir, logger=logger, filterre=filterre) if not found: raise ArgumentError("You're trying to deploy a sandbox to a " "domain that does not contain the commit " "where the sandbox was branched from.") kingdir = self.config.get("broker", "kingdir") rundir = self.config.get("broker", "rundir") tempdir = mkdtemp(prefix="deploy_", suffix="_%s" % dbsource.name, dir=rundir) try: run_git(["clone", "--shared", "--branch", dbtarget.name, kingdir, dbtarget.name], path=tempdir, logger=logger) temprepo = os.path.join(tempdir, dbtarget.name) # We could try to use fmt-merge-msg but its usage is so obscure that # faking it is easier merge_msg = [] merge_msg.append("Merge remote branch 'origin/%s' into %s" % (dbsource.name, dbtarget.name)) merge_msg.append("") merge_msg.append("User: %s" % user) merge_msg.append("Request ID: %s" % requestid) if justification: merge_msg.append("Justification: %s" % justification) if comments: merge_msg.append("Comments: %s" % comments) try: run_git(["merge", "--no-ff", "origin/%s" % dbsource.name, "-m", "\n".join(merge_msg)], path=temprepo, logger=logger, loglevel=CLIENT_INFO) except ProcessException, e: # No need to re-print e, output should have gone to client # immediately via the logger. raise ArgumentError("Failed to merge changes from %s into %s" % (dbsource.name, dbtarget.name)) # FIXME: Run tests before pushing back to template-king. # Use a different try/except and a specific error message. if dryrun: session.rollback() return run_git(["push", "origin", dbtarget.name], path=temprepo, logger=logger)
def main(): print "Calculating sandbox base commits. This may take around 10 minutes." logging.basicConfig(level=logging.WARNING) kingdir = config.get("broker", "kingdir") domains = session.query(Domain).all() # Define preference order when multiple domains have the same commits. # This is just cosmetics, but makes it easier to verify the output. for idx, domain in enumerate( ("prod", "qa", "secure-aquilon-prod", "secure-aquilon-qa")): dbdom = Domain.get_unique(session, domain, compel=True) domains.remove(dbdom) domains.insert(idx, dbdom) base_commits = {} q = session.query(Sandbox) q = q.order_by('name') # The base_commit column does not exist yet... q = q.options(defer("base_commit")) for sandbox in q: base_domain = None base_commit = None min_ahead = None commits = run_git(["rev-list", "refs/heads/" + sandbox.name], path=kingdir).split("\n") for domain in domains: merge_base = run_git([ "merge-base", "refs/heads/" + sandbox.name, "refs/heads/" + domain.name ], path=kingdir).strip() # Number of commits since branching from the given domain ahead = commits.index(merge_base) if base_domain is None or ahead < min_ahead: base_domain = domain base_commit = merge_base min_ahead = ahead if min_ahead == 0: break print "{0: <40}: {1.name} (ahead {2})".format(sandbox, base_domain, min_ahead) base_commits[sandbox.name] = base_commit session.expunge_all() try: if session.bind.dialect.name == 'oracle': query = text(""" ALTER TABLE sandbox ADD base_commit VARCHAR2(40 CHAR) """) elif session.bind.dialect.name == 'postgresql': query = text(""" ALTER TABLE sandbox ADD base_commit CHARACTER VARYING (40) """) print "\nExecuting: %s" % query session.execute(query) session.commit() except DatabaseError: # Allow the script to be re-run by not failing if the column already # exists. If the column does not exist, then trying to update it will # fail anyway. print """ WARNING: Adding the sandbox.base_commit column has failed. If you're running this script for the second time, then that's likely OK, otherwise you should verify and correct the schema manually. """ session.rollback() for sandbox in q: sandbox.base_commit = base_commits[sandbox.name] session.commit() try: if session.bind.dialect.name == 'oracle': query = text(""" ALTER TABLE sandbox MODIFY (base_commit VARCHAR2(40 CHAR) CONSTRAINT sandbox_base_commit_nn NOT NULL) """) elif session.bind.dialect.name == 'postgresql': query = text(""" ALTER TABLE sandbox ALTER COLUMN base_commit SET NOT NULL """) print "\nExecuting: %s" % query session.execute(query) session.commit() except DatabaseError: print """ WARNING: Enabling the NOT NULL constraint for sandbox.base_commit column has failed. If you're running this script for the second time, then that's likely OK, otherwise you should verify and correct the schema manually. """ session.rollback()
def render(self, session, logger, dbuser, domain, track, start, change_manager, comments, allow_manage, **arguments): if not dbuser: raise AuthorizationException("Cannot create a domain without " "an authenticated connection.") Branch.get_unique(session, domain, preclude=True) valid = re.compile('^[a-zA-Z0-9_.-]+$') if (not valid.match(domain)): raise ArgumentError("Domain name '%s' is not valid." % domain) # FIXME: Verify that track is a valid branch name? # Or just let the branch command fail? compiler = self.config.get("panc", "pan_compiler") dbtracked = None if track: dbtracked = Branch.get_unique(session, track, compel=True) if getattr(dbtracked, "tracked_branch", None): raise ArgumentError("Cannot nest tracking. Try tracking " "{0:l} directly.".format(dbtracked.tracked_branch)) start_point = dbtracked if change_manager: raise ArgumentError("Cannot enforce a change manager for " "tracking domains.") else: if not start: start = self.config.get("broker", "default_domain_start") start_point = Branch.get_unique(session, start, compel=True) dbdomain = Domain(name=domain, owner=dbuser, compiler=compiler, tracked_branch=dbtracked, requires_change_manager=bool(change_manager), comments=comments) session.add(dbdomain) if allow_manage is not None: dbdomain.allow_manage = allow_manage session.flush() domainsdir = self.config.get("broker", "domainsdir") clonedir = os.path.join(domainsdir, dbdomain.name) if os.path.exists(clonedir): raise InternalError("Domain directory already exists") kingdir = self.config.get("broker", "kingdir") cmd = ["branch"] if track: cmd.append("--track") else: cmd.append("--no-track") cmd.append(dbdomain.name) cmd.append(start_point.name) run_git(cmd, path=kingdir, logger=logger) # If the branch command above fails the DB will roll back as normal. # If the command below fails we need to clean up from itself and above. try: run_git(["clone", "--branch", dbdomain.name, kingdir, dbdomain.name], path=domainsdir, logger=logger) except ProcessException, e: try: remove_dir(clonedir, logger=logger) run_git(["branch", "-D", dbdomain.name], path=kingdir, logger=logger) except ProcessException, e2: logger.info("Exception while cleaning up: %s", e2)
def render(self, session, logger, source, target, sync, dryrun, comments, justification, user, requestid, **arguments): # Most of the logic here is duplicated in publish dbsource = Branch.get_unique(session, source, compel=True) # The target has to be a non-tracking domain dbtarget = Domain.get_unique(session, target, compel=True) if sync and isinstance(dbtarget.tracked_branch, Domain) \ and dbtarget.tracked_branch.autosync and dbtarget.autosync: # The user probably meant to deploy to the tracked branch, # but only do so if all the relevant autosync flags are # positive. logger.warning("Deploying to tracked branch %s and then will " "auto-sync %s" % (dbtarget.tracked_branch.name, dbtarget.name)) dbtarget = dbtarget.tracked_branch elif dbtarget.tracked_branch: raise ArgumentError("Cannot deploy to tracking domain %s. " "Did you mean domain %s?" % (dbtarget.name, dbtarget.tracked_branch.name)) if sync and not dbtarget.is_sync_valid and dbtarget.trackers: # FIXME: Maybe raise an ArgumentError and request that the # command run with --nosync? Maybe provide a --validate flag? # For now, just auto-flip (below). pass if not dbtarget.is_sync_valid: dbtarget.is_sync_valid = True if dbtarget.requires_change_manager: if not justification: raise AuthorizationException( "{0} is under change management control. Please specify " "--justification.".format(dbtarget)) validate_justification(user, justification) if isinstance(dbsource, Sandbox): domainsdir = self.config.get('broker', 'domainsdir') targetdir = os.path.join(domainsdir, dbtarget.name) filterre = re.compile('^' + dbsource.base_commit + '$') found = run_git(['rev-list', 'HEAD'], path=targetdir, logger=logger, filterre=filterre) if not found: raise ArgumentError("You're trying to deploy a sandbox to a " "domain that does not contain the commit " "where the sandbox was branched from.") kingdir = self.config.get("broker", "kingdir") rundir = self.config.get("broker", "rundir") tempdir = mkdtemp(prefix="deploy_", suffix="_%s" % dbsource.name, dir=rundir) try: run_git([ "clone", "--shared", "--branch", dbtarget.name, kingdir, dbtarget.name ], path=tempdir, logger=logger) temprepo = os.path.join(tempdir, dbtarget.name) # We could try to use fmt-merge-msg but its usage is so obscure that # faking it is easier merge_msg = [] merge_msg.append("Merge remote branch 'origin/%s' into %s" % (dbsource.name, dbtarget.name)) merge_msg.append("") merge_msg.append("User: %s" % user) merge_msg.append("Request ID: %s" % requestid) if justification: merge_msg.append("Justification: %s" % justification) if comments: merge_msg.append("Comments: %s" % comments) try: run_git([ "merge", "--no-ff", "origin/%s" % dbsource.name, "-m", "\n".join(merge_msg) ], path=temprepo, logger=logger, loglevel=CLIENT_INFO) except ProcessException, e: # No need to re-print e, output should have gone to client # immediately via the logger. raise ArgumentError("Failed to merge changes from %s into %s" % (dbsource.name, dbtarget.name)) # FIXME: Run tests before pushing back to template-king. # Use a different try/except and a specific error message. if dryrun: session.rollback() return run_git(["push", "origin", dbtarget.name], path=temprepo, logger=logger)
def render(self, session, logger, dbuser, domain, track, start, change_manager, comments, allow_manage, **arguments): if not dbuser: raise AuthorizationException("Cannot create a domain without " "an authenticated connection.") Branch.get_unique(session, domain, preclude=True) valid = re.compile('^[a-zA-Z0-9_.-]+$') if (not valid.match(domain)): raise ArgumentError("Domain name '%s' is not valid." % domain) # FIXME: Verify that track is a valid branch name? # Or just let the branch command fail? compiler = self.config.get("panc", "pan_compiler") dbtracked = None if track: dbtracked = Branch.get_unique(session, track, compel=True) if getattr(dbtracked, "tracked_branch", None): raise ArgumentError("Cannot nest tracking. Try tracking " "{0:l} directly.".format( dbtracked.tracked_branch)) start_point = dbtracked if change_manager: raise ArgumentError("Cannot enforce a change manager for " "tracking domains.") else: if not start: start = self.config.get("broker", "default_domain_start") start_point = Branch.get_unique(session, start, compel=True) dbdomain = Domain(name=domain, owner=dbuser, compiler=compiler, tracked_branch=dbtracked, requires_change_manager=bool(change_manager), comments=comments) session.add(dbdomain) if allow_manage is not None: dbdomain.allow_manage = allow_manage session.flush() domainsdir = self.config.get("broker", "domainsdir") clonedir = os.path.join(domainsdir, dbdomain.name) if os.path.exists(clonedir): raise InternalError("Domain directory already exists") kingdir = self.config.get("broker", "kingdir") cmd = ["branch"] if track: cmd.append("--track") else: cmd.append("--no-track") cmd.append(dbdomain.name) cmd.append(start_point.name) run_git(cmd, path=kingdir, logger=logger) # If the branch command above fails the DB will roll back as normal. # If the command below fails we need to clean up from itself and above. try: run_git( ["clone", "--branch", dbdomain.name, kingdir, dbdomain.name], path=domainsdir, logger=logger) except ProcessException, e: try: remove_dir(clonedir, logger=logger) run_git(["branch", "-D", dbdomain.name], path=kingdir, logger=logger) except ProcessException, e2: logger.info("Exception while cleaning up: %s", e2)