|  | @@ -1,110 +1,6 @@
 | 
	
		
			
				|  |  |  """
 | 
	
		
			
				|  |  | -Frank can be configured using the following yaml::
 | 
	
		
			
				|  |  | -    .frank.yml
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    commads:
 | 
	
		
			
				|  |  | -     - test
 | 
	
		
			
				|  |  | -     - deploy
 | 
	
		
			
				|  |  | -     - publish
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    newtag:
 | 
	
		
			
				|  |  | -         - python:
 | 
	
		
			
				|  |  | -           - frank.actions:detect_new_tag
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        deploy:
 | 
	
		
			
				|  |  | -         - cmd
 | 
	
		
			
				|  |  | -         - runif
 | 
	
		
			
				|  |  | -           - test
 | 
	
		
			
				|  |  | -           - newtag
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        publish:
 | 
	
		
			
				|  |  | -          - shell:
 | 
	
		
			
				|  |  | -           - cd docs; make html
 | 
	
		
			
				|  |  | -          - python:
 | 
	
		
			
				|  |  | -           - frank.actions:recursive_copy
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -The sections commands is a simple list of command you would
 | 
	
		
			
				|  |  | -like to define.
 | 
	
		
			
				|  |  | -This section is optional. It's main purpose is for you to decalre
 | 
	
		
			
				|  |  | -which commands frank should execute upon recieving a load.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -Each command is a dictionary with the following possible keys::
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    cmd_name:
 | 
	
		
			
				|  |  | -      - cmd  # not mandatory if you include it in the list of commands
 | 
	
		
			
				|  |  | -      - runif # a conditional to determine whether to run or skip the command
 | 
	
		
			
				|  |  | -              # it can contain multiple directives which can be python
 | 
	
		
			
				|  |  | -              # code to execute
 | 
	
		
			
				|  |  | -              # or shell code
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -For example, let's say you would like to build the sphinx documentation
 | 
	
		
			
				|  |  | -of your project after every push. You start by defining a command
 | 
	
		
			
				|  |  | -build_docs in the following way::
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -   build_docs:
 | 
	
		
			
				|  |  | -    - cmd
 | 
	
		
			
				|  |  | -    - shell:
 | 
	
		
			
				|  |  | -      - cd docs
 | 
	
		
			
				|  |  | -      - make html
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -You could also specify::
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -   build_docs:
 | 
	
		
			
				|  |  | -    - cmd
 | 
	
		
			
				|  |  | -    - shell: cd docs; make html
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -or::
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -   build_docs:
 | 
	
		
			
				|  |  | -    - shell:
 | 
	
		
			
				|  |  | -      - cwd: docs
 | 
	
		
			
				|  |  | -      - cmd: make html
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -This tells that `build_docs` is a command that executes after every push.
 | 
	
		
			
				|  |  | -It will execute a shell and will run the shell commands
 | 
	
		
			
				|  |  | -`cd docs; make html`. Pretty straight forward!
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -Now, let's refine. Suppose we want to build the docs only if the docs changed,
 | 
	
		
			
				|  |  | -thus ignoring completely, changes in the embeded docs strings*::
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -   build_docs:
 | 
	
		
			
				|  |  | -    - cmd
 | 
	
		
			
				|  |  | -    - runif:
 | 
	
		
			
				|  |  | -      - shell:
 | 
	
		
			
				|  |  | -        - git --no-pager diff --name-status HEAD~1 | grep -v docs
 | 
	
		
			
				|  |  | -    - shell: cd docs; make html
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -Now, the command will run if the latest git commit have changed the docs
 | 
	
		
			
				|  |  | -directory. Since the grep command will return exit with 1.
 | 
	
		
			
				|  |  | -Alternatively, the conditional to run can be some python code that returns
 | 
	
		
			
				|  |  | -True or False. Here is how to specify this:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -   build_docs:
 | 
	
		
			
				|  |  | -    - cmd
 | 
	
		
			
				|  |  | -    - runif:
 | 
	
		
			
				|  |  | -      - python: frank.githubparsers:detect_changes_in_docs
 | 
	
		
			
				|  |  | -    - shell: cd docs; make html
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -This, will invoke the method ``detect_changes_in_docs`` which is
 | 
	
		
			
				|  |  | -found in the module `frank.githubparsers`.
 | 
	
		
			
				|  |  | -If this function will return True the shell command will execute.
 | 
	
		
			
				|  |  | -If the methods takes some arguments, they could also be given.
 | 
	
		
			
				|  |  | -Suppose the function is called `detect_changes_in` and this function
 | 
	
		
			
				|  |  | -takes a single paramter called path we can configure the
 | 
	
		
			
				|  |  | -build_docs command in the following way:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -   build_docs:
 | 
	
		
			
				|  |  | -    - cmd
 | 
	
		
			
				|  |  | -    - runif:
 | 
	
		
			
				|  |  | -      - python:
 | 
	
		
			
				|  |  | -        - function: frank.githubparsers:detect_changes_in
 | 
	
		
			
				|  |  | -        - args:
 | 
	
		
			
				|  |  | -          - path: docs
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -* This is probably a lame idea, but it's good for demonstration.
 | 
	
		
			
				|  |  | -So, do write doc-strings, and do build  your docs often.
 | 
	
		
			
				|  |  | +Frank-ci - A simple CI for Pythonistas, based on Flask + Huey.
 | 
	
		
			
				|  |  | +For more information see the docs/frank.rst 
 | 
	
		
			
				|  |  |  """
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  import hmac
 | 
	
	
		
			
				|  | @@ -286,7 +182,42 @@ def clone(clone_url, branch, depth=1):
 | 
	
		
			
				|  |  |  @taskq.task()
 | 
	
		
			
				|  |  |  def count_beans():
 | 
	
		
			
				|  |  |      print "count_12"
 | 
	
		
			
				|  |  | -    return "count_12"
 | 
	
		
			
				|  |  | +     return "count_12"
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +@taskq.task()
 | 
	
		
			
				|  |  | +def build_task(request_json):
 | 
	
		
			
				|  |  | +    """ 
 | 
	
		
			
				|  |  | +     . clone the latest commit (--depth 1)
 | 
	
		
			
				|  |  | +     . parse yaml config
 | 
	
		
			
				|  |  | +     . for each command in the config
 | 
	
		
			
				|  |  | +     .     run command
 | 
	
		
			
				|  |  | +     .     report success or failure
 | 
	
		
			
				|  |  | +    """
 | 
	
		
			
				|  |  | +    request_as_json = request.get_json()
 | 
	
		
			
				|  |  | +    clone_dest = parse_branch_gh(request_as_json)
 | 
	
		
			
				|  |  | +    repo_name = request_as_json["repository"]['name']
 | 
	
		
			
				|  |  | +    try:
 | 
	
		
			
				|  |  | +        o, e = clone(request_as_json['repository']['ssh_url'], clone_dest)
 | 
	
		
			
				|  |  | +    except Exception as E:
 | 
	
		
			
				|  |  | +        print E, E.message
 | 
	
		
			
				|  |  | +    # parse yaml is still very crude ...
 | 
	
		
			
				|  |  | +    # it could yield selfect with a run method
 | 
	
		
			
				|  |  | +    # thus:
 | 
	
		
			
				|  |  | +    # for action in parse_yaml(clone_dest):
 | 
	
		
			
				|  |  | +    #     action.run()
 | 
	
		
			
				|  |  | +    #
 | 
	
		
			
				|  |  | +    # this should also handle dependencies,
 | 
	
		
			
				|  |  | +    # the following implementation is very crud
 | 
	
		
			
				|  |  | +    failed = None
 | 
	
		
			
				|  |  | +    for action in parse_yaml(clone_dest):
 | 
	
		
			
				|  |  | +        # if config says we use huey, we should modiy run_action
 | 
	
		
			
				|  |  | +        results = run_action(action)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    if any([result.code for result in results]):
 | 
	
		
			
				|  |  | +        report_failure(results)
 | 
	
		
			
				|  |  | +    else:
 | 
	
		
			
				|  |  | +        report_success(results)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  @app.route('/beans')
 | 
	
	
		
			
				|  | @@ -308,44 +239,19 @@ def start():
 | 
	
		
			
				|  |  |      """
 | 
	
		
			
				|  |  |      main logic:
 | 
	
		
			
				|  |  |       1. listen to post
 | 
	
		
			
				|  |  | -     2a if authenticated post do:
 | 
	
		
			
				|  |  | -     3.    clone the latest commit (--depth 1)
 | 
	
		
			
				|  |  | -     4.    parse yaml config
 | 
	
		
			
				|  |  | -     5.    for each command in the config
 | 
	
		
			
				|  |  | -     7.        run command
 | 
	
		
			
				|  |  | -     8.        report success or failure
 | 
	
		
			
				|  |  | -    """
 | 
	
		
			
				|  |  | +     2.  if authenticated post do:
 | 
	
		
			
				|  |  | +           enqueue task to build or test the code
 | 
	
		
			
				|  |  |      # This is authentication for github only
 | 
	
		
			
				|  |  |      # We could\should check for other hostings
 | 
	
		
			
				|  |  | +    """
 | 
	
		
			
				|  |  |      ans = hmac.new(app.config['POST_KEY'], request.data,
 | 
	
		
			
				|  |  |                     hashlib.sha1).hexdigest()
 | 
	
		
			
				|  |  |      secret = request.headers['X-Hub-Signature'].split('=')[-1]
 | 
	
		
			
				|  |  |      if ans != secret:
 | 
	
		
			
				|  |  |          return abort(500)
 | 
	
		
			
				|  |  |      request_as_json = request.get_json()
 | 
	
		
			
				|  |  | -    clone_dest = parse_branch_gh(request_as_json)
 | 
	
		
			
				|  |  | -    repo_name = request_as_json["repository"]['name']
 | 
	
		
			
				|  |  | -    try:
 | 
	
		
			
				|  |  | -        o, e = clone(request_as_json['repository']['ssh_url'], clone_dest)
 | 
	
		
			
				|  |  | -    except Exception as E:
 | 
	
		
			
				|  |  | -        print E, E.message
 | 
	
		
			
				|  |  | -    # parse yaml is still very crude ...
 | 
	
		
			
				|  |  | -    # it could yield selfect with a run method
 | 
	
		
			
				|  |  | -    # thus:
 | 
	
		
			
				|  |  | -    # for action in parse_yaml(clone_dest):
 | 
	
		
			
				|  |  | -    #     action.run()
 | 
	
		
			
				|  |  | -    #
 | 
	
		
			
				|  |  | -    # this should also handle dependencies,
 | 
	
		
			
				|  |  | -    # the following implementation is very crud
 | 
	
		
			
				|  |  | -    failed = None
 | 
	
		
			
				|  |  | -    for action in parse_yaml(clone_dest):
 | 
	
		
			
				|  |  | -        # if config says we use huey, we should modiy run_action
 | 
	
		
			
				|  |  | -        results = run_action(action)
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    if any([result.code for result in results]):
 | 
	
		
			
				|  |  | -        report_failure(results)
 | 
	
		
			
				|  |  | -    else:
 | 
	
		
			
				|  |  | -        report_success(results)
 | 
	
		
			
				|  |  | +    build_task(request_as_json)
 | 
	
		
			
				|  |  | +    return "OK"
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  @click.group()
 |