{"action":"created","comment":{"url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/pulls/comments/75263975","id":75263975,"diff_hunk":"@@ -0,0 +1,522 @@\n+# -*- coding: utf-8 -*-\n+\n+import re\n+import botocore\n+import boto3\n+import sys\n+import os\n+import json\n+import subprocess\n+import urllib2\n+\n+import StringIO\n+import gzip\n+from datetime import datetime\n+from textwrap import dedent\n+import mimetypes\n+\n+\n+def usage():\n+    print(dedent('''\\\n+        Manage map.geo.admin.ch versions in AWS S3 bucket. Please make sure all your env variables are set.\n+        (namely S3_MF_GEOADMIN3_INFRA)\n+\n+        Usage:\n+\n+            .build-artefacts/python-venv/bin/python scripts/s3manage.py\n+                                                    <upload|list|info|activate|delete> (cmd type)\n+                                                    <options>\n+\n+        A verions deployed to S3 is always defined by:\n+\n+        <s3version> = <branch_name>/<sha>/<version>\n+\n+        Commands:\n+\n+            upload:   Upload content of /prd (and /src) directory to a bucket.\n+                      You may specify a directory (it defaults to current).\n+\n+                      Example: python scripts/s3manage.py upload <snapshotdir> <deploy_target>\n+\n+            list:     List available <version> in a bucket.\n+\n+                      Example: python scripts/s3manage.py list\n+\n+            info:     Print the info.json file.\n+\n+                      Example: python scripts/s3manage.py info <s3version>\n+\n+            activate: Activate a version at the root of a bucket.\n+\n+                      Example: python scripts/s3manage.py activate <s3version> <deploy_target>\n+\n+            delete:   Delete an existing project.\n+\n+                      Example: python scripts/s3manage.py delete <s3version>\n+    '''))\n+\n+\n+mimetypes.init()\n+mimetypes.add_type('application/x-font-ttf', '.ttf')\n+mimetypes.add_type('application/x-font-opentype', '.otf')\n+mimetypes.add_type('application/vnd.ms-fontobject', '.eot')\n+mimetypes.add_type('application/json', '.json')\n+mimetypes.add_type('text/cache-manifest', '.appcache')\n+mimetypes.add_type('text/plain', '.txt')\n+\n+NO_COMPRESS = [\n+    'image/png',\n+    'image/jpeg',\n+    'image/x-icon',\n+    'image/vnd.microsoft.icon',\n+    'application/x-font-ttf',\n+    'application/x-font-opentype',\n+    'application/vnd.ms-fontobject']\n+\n+\n+def local_git_last_commit(basedir):\n+    try:\n+        output = subprocess.check_output(('git rev-parse HEAD',), cwd=basedir, shell=True)\n+        return output.strip()\n+    except subprocess.CalledProcessError:\n+        print('Not a git directory: %s' % basedir)\n+    try:\n+        with open(os.path.join(basedir, '.build-artefacts', 'last-commit-ref'), 'r') as f:\n+            data = f.read()\n+        return data\n+    except IOError:\n+        print('Error while reading \\'last-commit-ref\\' from %s' % basedir)\n+    return None\n+\n+\n+def local_git_branch(basedir):\n+    output = subprocess.check_output(('git rev-parse --abbrev-ref HEAD',), cwd=basedir, shell=True)\n+    return output.strip()\n+\n+\n+def local_last_version(basedir):\n+    try:\n+        with open(os.path.join(basedir, '.build-artefacts', 'last-version'), 'r') as f:\n+            data = f.read()\n+        return data\n+    except IOError as e:\n+        print('Cannot find version: %s' % e)\n+    return None\n+\n+\n+def _gzip_data(data):\n+    out = None\n+    infile = StringIO.StringIO()\n+    try:\n+        gzip_file = gzip.GzipFile(fileobj=infile, mode='w', compresslevel=5)\n+        gzip_file.write(data)\n+        gzip_file.close()\n+        infile.seek(0)\n+        out = infile.getvalue()\n+    except:\n+        out = None\n+    finally:\n+        infile.close()\n+    return out\n+\n+\n+def _unzip_data(compressed):\n+    inbuffer = StringIO.StringIO(compressed)\n+    f = gzip.GzipFile(mode='rb', fileobj=inbuffer)\n+    try:\n+        data = f.read()\n+    finally:\n+        f.close()\n+\n+    return data\n+\n+\n+def save_to_s3(src, dest, bucket_name, cached=True, mimetype=None, break_on_error=False):\n+    try:\n+        with open(src, 'r') as f:\n+            data = f.read()\n+    except EnvironmentError as e:\n+        print('Failed to upload %s' % src)\n+        print(str(e))\n+        if break_on_error:\n+            print(\"Exiting...\")\n+            sys.exit(1)\n+        else:\n+            return False\n+    _save_to_s3(data, dest, mimetype, bucket_name, cached=cached)\n+\n+\n+def _save_to_s3(in_data, dest, mimetype, bucket_name, compress=True, cached=True):\n+    data = in_data\n+    compressed = False\n+    content_encoding = None\n+    cache_control = 'max-age=31536000, public'\n+    extra_args = {}\n+\n+    if compress and mimetype not in NO_COMPRESS:\n+        data = _gzip_data(in_data)\n+        content_encoding = 'gzip'\n+        compressed = True\n+\n+    if cached is False:\n+        cache_control = 'no-cache, no-store, max-age=0, must-revalidate'\n+\n+    extra_args['ACL'] = 'public-read'\n+    extra_args['ContentType'] = mimetype\n+    extra_args['CacheControl'] = cache_control\n+\n+    try:\n+        print('Uploading to %s - %s, gzip: %s, cache headers: %s' % (dest, mimetype, compressed, cached))\n+        if compressed:\n+            extra_args['ContentEncoding'] = content_encoding\n+\n+        if cached is False:\n+            extra_args['Expires'] = datetime(1990, 1, 1)\n+            extra_args['Metadata'] = {'Pragma': 'no-cache', 'Vary': '*'}\n+\n+        s3.Object(bucket_name, dest).put(Body=data, **extra_args)\n+    except Exception as e:\n+        print('Error while uploading %s: %s' % (dest, e))\n+\n+\n+def get_index_version(c):\n+    version = None\n+    p = re.compile(ur'version: \\'(\\d+)\\'')\n+    match = re.findall(p, c)\n+    if len(match) > 0:\n+        version = int(match[0])\n+    return version\n+\n+\n+def create_s3_dir_path(base_dir):\n+    git_short_sha = local_git_last_commit(base_dir)[:7]\n+    git_branch = local_git_branch(base_dir)\n+    version = local_last_version(base_dir).strip()\n+    return (os.path.join(git_branch, git_short_sha, version), version)\n+\n+\n+def is_cached(file_name):\n+    # 1 exception\n+    if file_name == 'services':\n+        return True\n+    _, extension = os.path.splitext(file_name)\n+    return bool(extension not in ['.html', '.txt', '.appcache', ''])\n+\n+\n+def get_file_mimetype(local_file):\n+    if local_file.endswith('services'):\n+        return 'application/json'\n+    else:\n+        mimetype, _ = mimetypes.guess_type(local_file)\n+        if mimetype:\n+            return mimetype\n+        return 'text/plain'\n+\n+\n+def upload(bucket_name, base_dir, deploy_target):\n+    s3_dir_path, version = create_s3_dir_path(base_dir)\n+    print('Destionation folder is:')\n+    print('%s' % s3_dir_path)\n+    upload_directories = ['prd', 'src']\n+    exclude_filename_patterns = ['.less', '.gitignore', '.mako.']\n+    root_files = ('index.html', 'mobile.html', 'embed.html',\n+                  'robots.txt', 'robots_prod.txt',\n+                  'checker', 'geoadmin.%s.appcache' % version)\n+\n+    for directory in upload_directories:\n+        for file_path_list in os.walk(os.path.join(base_dir, directory)):\n+            file_names = file_path_list[2]\n+            if len(file_names) > 0:\n+                for file_name in file_names:\n+                    file_base_path = file_path_list[0]\n+                    if len([p for p in exclude_filename_patterns if p in file_name]) == 0:\n+                        is_chsdi_cache = bool(file_base_path.endswith('cache'))\n+                        local_file = os.path.join(file_base_path, file_name)\n+                        file_base_path = file_base_path.replace('cache', '')\n+                        if directory == 'prd':\n+                            if file_name in root_files:\n+                                file_base_path = file_base_path.replace('prd', '')\n+                            else:\n+                                file_base_path = file_base_path.replace('prd', version)\n+                        relative_file_path = file_base_path.replace(base_dir + '/', '')\n+                        remote_file = os.path.join(s3_dir_path, relative_file_path, file_name)\n+                        # Don't cache some files\n+                        cached = is_cached(file_name)\n+                        mimetype = get_file_mimetype(local_file)\n+                        save_to_s3(local_file, remote_file, bucket_name, cached=cached, mimetype=mimetype)\n+                        # Also upload chsdi metadata file to src folder if available\n+                        if is_chsdi_cache:\n+                            relative_file_path = relative_file_path.replace(version + '/', '')\n+                            remote_file = os.path.join(s3_dir_path, 'src/', relative_file_path, file_name)\n+                            save_to_s3(local_file, remote_file, bucket_name, cached=cached, mimetype=mimetype)\n+\n+    url_to_check = '//mf-geoadmin3.%s.bgdi.ch/' % deploy_target\n+    # TODO Remove me!\n+    url_to_check = 'https://mf-geoadmin3.infra.bgdi.ch/'\n+    print('Upload completed at %s%s/index.html' % (url_to_check, s3_dir_path))\n+\n+\n+def get_head_sha(branch):\n+    b = branch.replace('/', '')\n+    try:\n+        resp = urllib2.urlopen(\n+            'https://api.github.com/repos/geoadmin/mf-geoadmin3/commits?sha=%s' % b)\n+        data = json.load(resp)\n+    except urllib2.HTTPError:\n+        data = None\n+        print('Branch %s not found.' % b)\n+    if data:\n+        return data[0]['sha']\n+\n+\n+def list_version():\n+    branches = bucket.meta.client.list_objects(Bucket=bucket.name,\n+                                               Delimiter='/')\n+    for b in branches.get('CommonPrefixes'):\n+        head_sha = None\n+        branch = b.get('Prefix')\n+        if re.search(r'^\\D', branch):\n+            shas = bucket.meta.client.list_objects(Bucket=bucket.name,\n+                                                   Prefix=branch,\n+                                                   Delimiter='/')\n+            shas = shas.get('CommonPrefixes')\n+            if shas:\n+                for s in shas:\n+                    sha = s.get('Prefix')\n+                    nice_sha = sha.replace(branch, '').replace('/', '')\n+\n+                    if head_sha is None:\n+                        head_sha = get_head_sha(branch)\n+                        if head_sha:\n+                            is_head = 'HEAD' if nice_sha in head_sha else 'NOT HEAD'\n+\n+                    if head_sha:\n+                        print(branch)\n+                        print('  {} - {} ({})'.format(nice_sha, is_head, head_sha))\n+                        builds = bucket.meta.client.list_objects(Bucket=bucket.name,\n+                                                                 Prefix=sha,\n+                                                                 Delimiter='/')\n+                        for v in builds.get('CommonPrefixes'):\n+                            build = v.get('Prefix')\n+                            print('    ' + build.replace(sha, ''))\n+            else:\n+                print('Not a official path for branch %s' % branch)\n+\n+\n+def get_version_info(s3_path):\n+    print('App version is: %s' % s3_path)\n+    version_target = s3_path.split('/')[2]\n+    obj = s3.Object(bucket.name, '%s/%s/info.json' % (s3_path, version_target))\n+    try:\n+        content = obj.get()[\"Body\"].read()\n+        raw = _unzip_data(content)\n+        data = json.loads(raw)\n+    except botocore.exceptions.ClientError:\n+        return None\n+    except botocore.exceptions.BotoCoreError:\n+        return None\n+    return data\n+\n+\n+def version_info(s3_path):\n+    info = get_version_info(s3_path)\n+    if info is None:\n+        print('No info for version %s' % s3_path)\n+        sys.exit(1)\n+    for k in info.keys():\n+        print('%s: %s' % (k, info[k]))\n+\n+\n+def version_exists(s3_path):\n+    files = bucket.objects.filter(Prefix=str(s3_path)).all()\n+    return len(list(files)) > 0\n+\n+\n+def delete_version(s3_path, bucket_name):\n+    if version_exists(s3_path) is False:\n+        print('Version <%s> does not exists in AWS S3. Aborting' % s3_path)\n+        sys.exit(1)\n+\n+    msg = raw_input('Are you sure you want to delete all files in <%s>?\\n' % s3_path)\n+    if msg.lower() in ('y', 'yes'):\n+        files = bucket.objects.filter(Prefix=str(s3_path)).all()\n+\n+        indexes = [{'Key': k.key} for k in files]\n+        for n in ('index', 'embed', 'mobile'):\n+            src_key_name = '{}.{}.html'.format(n, s3_path)\n+            indexes.append({'Key': src_key_name})\n+\n+        resp = s3client.delete_objects(Bucket=bucket_name, Delete={'Objects': indexes})\n+        for v in resp['Deleted']:\n+            print(v)\n+    else:\n+        print('Aborting deletion of <%s>.' % s3_path)\n+\n+\n+def get_url(bucket_name='mf-geoadmin3.infra.bgdi.ch', key_name='index.html'):\n+    object_url = 'https://%s/%s' % (bucket_name, key_name)\n+    return object_url\n+\n+\n+def activate_version(s3_path, bucket_name, deploy_target):\n+    if version_exists(s3_path) is False:\n+        print('Version <%s> does not exists in AWS S3. Aborting' % s3_path)\n+        sys.exit(1)\n+\n+    msg = raw_input('Are you sure you want to activate version <%s>?\\n' % s3_path)\n+    if msg.lower() in ('y', 'yes'):\n+        # Prod files\n+        for n in ('index', 'embed', 'mobile'):\n+            src_key_name = '{}/{}.html'.format(s3_path, n)\n+            print('{} --> {}.html'.format(src_key_name, n))\n+            s3client.copy_object(\n+                Bucket=bucket_name,\n+                CopySource=bucket_name + '/' + src_key_name,\n+                Key=n + '.html',\n+                ACL='public-read')\n+        # Delete older appcache files\n+        appcache_versioned_files = list(bucket.objects.filter(Prefix='geoadmin.').all())\n+        indexes = [{'Key': k.key} for k in appcache_versioned_files if k.key.endswith('.appcache')]\n+        if len(indexes) > 0:\n+            s3client.delete_objects(Bucket=bucket_name, Delete={'Objects': indexes})\n+\n+        appcache = None\n+        files = list(bucket.objects.filter(Prefix='{}/geoadmin.'.format(s3_path)).all())\n+        if len(files) > 0:\n+            appcache = os.path.basename(sorted(files)[-1].key)\n+        for j in ('robots.txt', 'checker', appcache):\n+            # In prod move robots prod\n+            src_file_name = 'robots_prod.txt' if j == 'robots.txt' and deploy_target == 'prod' else j\n+            src_key_name = '{}/{}'.format(s3_path, src_file_name)\n+            print('%s ---> %s' % (src_key_name, j))\n+            try:\n+                s3client.copy_object(\n+                    Bucket=bucket_name,\n+                    CopySource=bucket_name + '/' + src_key_name,\n+                    Key=j,\n+                    CopySourceIfModifiedSince=datetime(2015, 1, 1),\n+                    ACL='public-read')\n+            except botocore.exceptions.ClientError as e:\n+                print('Cannot copy {}: {}'.format(j, e))\n+        # TODO Adapt get_url to final URLs\n+        print('\\nPlease check it on {}'.format(get_url()))\n+        print('And {}'.format(get_url(key_name='src/index.html')))\n+    else:\n+        print('Aborting activation of version {}'.format(s3_path))\n+        sys.exit(1)\n+\n+\n+def init_connection(bucket_name, profile_name):\n+    try:\n+        session = boto3.session.Session(profile_name=profile_name)\n+    except botocore.exceptions.ProfileNotFound as e:\n+        print('You need to set PROFILE_NAME to a valid profile name in $HOME/.aws/credentials')\n+        print(e)\n+        sys.exit(1)\n+    except botocore.exceptions.BotoCoreError as e:\n+        print('Cannot establish connection. Check you credentials %s.' % profile_name)\n+        print(e)\n+        sys.exit(1)\n+\n+    s3client = session.client('s3', config=boto3.session.Config(signature_version='s3v4'))\n+    s3 = session.resource('s3', config=boto3.session.Config(signature_version='s3v4'))\n+\n+    bucket = s3.Bucket(bucket_name)\n+    return (s3, s3client, bucket)\n+\n+\n+def exit_usage(cmd_type):\n+    print('Missing one arg for %s command' % cmd_type)\n+    usage()\n+    sys.exit(1)\n+\n+\n+def parse_arguments(argv):\n+    if len(argv) < 2:\n+        exit_usage('UNKNOWN')\n+\n+    cmd_type = str(argv[1])\n+    deploy_target = 'int'\n+\n+    supported_cmds = ('upload', 'list', 'info', 'activate', 'delete')\n+    if cmd_type not in supported_cmds:\n+        print('Command %s not supported' % cmd_type)\n+        usage()\n+        sys.exit(1)\n+\n+    if cmd_type == 'upload' and len(argv) != 4:\n+        exit_usage(cmd_type)\n+    elif cmd_type == 'list' and len(argv) != 2:\n+        exit_usage(cmd_type)\n+    elif cmd_type == 'info' and len(argv) != 3:\n+        exit_usage(cmd_type)\n+    elif cmd_type == 'activate' and len(argv) != 4:\n+        exit_usage(cmd_type)\n+    elif cmd_type == 'delete' and len(argv) != 3:\n+        exit_usage(cmd_type)\n+\n+    base_dir = os.getcwd()\n+    if cmd_type == 'upload':\n+        base_dir = os.path.abspath(argv[2])\n+        if not os.path.isdir(base_dir):\n+            print('No code found in directory %s' % base_dir)\n+            sys.exit(1)\n+\n+    if cmd_type in ('activate', 'upload'):\n+        deploy_target = argv[3]\n+        if deploy_target not in ('int', 'prod'):\n+            print('%s is not a valid deploy target' % deploy_target)\n+            sys.exit(1)\n+\n+    s3_path = None\n+    if cmd_type in ('info', 'activate', 'delete'):\n+        s3_path = argv[2]\n+        if s3_path.endswith('/'):\n+            s3_path = s3_path[:len(s3_path) - 1]\n+        if s3_path.count('/') != 2:\n+            print('Bad version definition')\n+            usage()\n+            sys.exit(1)\n+\n+    bucket_name_env = 'S3_MF_GEOADMIN3_%s' % deploy_target.upper()\n+    # TODO Remove me when switch has been performed\n+    bucket_name_env = 'S3_MF_GEOADMIN3_INFRA'","path":"scripts/s3manage.py","position":483,"original_position":483,"commit_id":"ca48331d711d93b6323d3d55f08a598d069abd9a","original_commit_id":"ca48331d711d93b6323d3d55f08a598d069abd9a","user":{"login":"gjn","id":2572317,"avatar_url":"https://avatars.githubusercontent.com/u/2572317?v=3","gravatar_id":"","url":"https://api.github.com/users/gjn","html_url":"https://github.com/gjn","followers_url":"https://api.github.com/users/gjn/followers","following_url":"https://api.github.com/users/gjn/following{/other_user}","gists_url":"https://api.github.com/users/gjn/gists{/gist_id}","starred_url":"https://api.github.com/users/gjn/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gjn/subscriptions","organizations_url":"https://api.github.com/users/gjn/orgs","repos_url":"https://api.github.com/users/gjn/repos","events_url":"https://api.github.com/users/gjn/events{/privacy}","received_events_url":"https://api.github.com/users/gjn/received_events","type":"User","site_admin":false},"body":"We should be able to write onto any bucket from this branch.\r\n\r\nThen we can start our varnish tests (dev, int ----merge --- prod)\r\n\r\nPlan?","created_at":"2016-08-18T08:04:05Z","updated_at":"2016-08-18T08:04:05Z","html_url":"https://github.com/geoadmin/mf-geoadmin3/pull/3410#discussion_r75263975","pull_request_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/pulls/3410","_links":{"self":{"href":"https://api.github.com/repos/geoadmin/mf-geoadmin3/pulls/comments/75263975"},"html":{"href":"https://github.com/geoadmin/mf-geoadmin3/pull/3410#discussion_r75263975"},"pull_request":{"href":"https://api.github.com/repos/geoadmin/mf-geoadmin3/pulls/3410"}}},"pull_request":{"url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/pulls/3410","id":78513508,"html_url":"https://github.com/geoadmin/mf-geoadmin3/pull/3410","diff_url":"https://github.com/geoadmin/mf-geoadmin3/pull/3410.diff","patch_url":"https://github.com/geoadmin/mf-geoadmin3/pull/3410.patch","issue_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/issues/3410","number":3410,"state":"open","locked":false,"title":"[WIP] Upload geoadmin to s3","user":{"login":"loicgasser","id":3306154,"avatar_url":"https://avatars.githubusercontent.com/u/3306154?v=3","gravatar_id":"","url":"https://api.github.com/users/loicgasser","html_url":"https://github.com/loicgasser","followers_url":"https://api.github.com/users/loicgasser/followers","following_url":"https://api.github.com/users/loicgasser/following{/other_user}","gists_url":"https://api.github.com/users/loicgasser/gists{/gist_id}","starred_url":"https://api.github.com/users/loicgasser/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/loicgasser/subscriptions","organizations_url":"https://api.github.com/users/loicgasser/orgs","repos_url":"https://api.github.com/users/loicgasser/repos","events_url":"https://api.github.com/users/loicgasser/events{/privacy}","received_events_url":"https://api.github.com/users/loicgasser/received_events","type":"User","site_admin":false},"body":"Based on https://github.com/geoadmin/mf-geoadmin3/pull/3142\r\nFixes rebase conflicts\r\nStart cleanup and new make targets\r\n\r\nping @procrastinatio ","created_at":"2016-07-22T17:38:43Z","updated_at":"2016-08-18T08:04:05Z","closed_at":null,"merged_at":null,"merge_commit_sha":"2ae7d3059faf5375204967a6a77e649a5b3c10f4","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/pulls/3410/commits","review_comments_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/pulls/3410/comments","review_comment_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/pulls/comments{/number}","comments_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/issues/3410/comments","statuses_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/statuses/ca48331d711d93b6323d3d55f08a598d069abd9a","head":{"label":"geoadmin:mom_s3_upload","ref":"mom_s3_upload","sha":"ca48331d711d93b6323d3d55f08a598d069abd9a","user":{"login":"geoadmin","id":3369793,"avatar_url":"https://avatars.githubusercontent.com/u/3369793?v=3","gravatar_id":"","url":"https://api.github.com/users/geoadmin","html_url":"https://github.com/geoadmin","followers_url":"https://api.github.com/users/geoadmin/followers","following_url":"https://api.github.com/users/geoadmin/following{/other_user}","gists_url":"https://api.github.com/users/geoadmin/gists{/gist_id}","starred_url":"https://api.github.com/users/geoadmin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/geoadmin/subscriptions","organizations_url":"https://api.github.com/users/geoadmin/orgs","repos_url":"https://api.github.com/users/geoadmin/repos","events_url":"https://api.github.com/users/geoadmin/events{/privacy}","received_events_url":"https://api.github.com/users/geoadmin/received_events","type":"Organization","site_admin":false},"repo":{"id":10399273,"name":"mf-geoadmin3","full_name":"geoadmin/mf-geoadmin3","owner":{"login":"geoadmin","id":3369793,"avatar_url":"https://avatars.githubusercontent.com/u/3369793?v=3","gravatar_id":"","url":"https://api.github.com/users/geoadmin","html_url":"https://github.com/geoadmin","followers_url":"https://api.github.com/users/geoadmin/followers","following_url":"https://api.github.com/users/geoadmin/following{/other_user}","gists_url":"https://api.github.com/users/geoadmin/gists{/gist_id}","starred_url":"https://api.github.com/users/geoadmin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/geoadmin/subscriptions","organizations_url":"https://api.github.com/users/geoadmin/orgs","repos_url":"https://api.github.com/users/geoadmin/repos","events_url":"https://api.github.com/users/geoadmin/events{/privacy}","received_events_url":"https://api.github.com/users/geoadmin/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/geoadmin/mf-geoadmin3","description":"map.geo.admin.ch source code","fork":false,"url":"https://api.github.com/repos/geoadmin/mf-geoadmin3","forks_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/forks","keys_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/keys{/key_id}","collaborators_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/teams","hooks_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/hooks","issue_events_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/issues/events{/number}","events_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/events","assignees_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/assignees{/user}","branches_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/branches{/branch}","tags_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/tags","blobs_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/git/refs{/sha}","trees_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/git/trees{/sha}","statuses_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/statuses/{sha}","languages_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/languages","stargazers_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/stargazers","contributors_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/contributors","subscribers_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/subscribers","subscription_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/subscription","commits_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/commits{/sha}","git_commits_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/git/commits{/sha}","comments_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/comments{/number}","issue_comment_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/issues/comments{/number}","contents_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/contents/{+path}","compare_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/compare/{base}...{head}","merges_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/merges","archive_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/downloads","issues_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/issues{/number}","pulls_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/pulls{/number}","milestones_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/milestones{/number}","notifications_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/labels{/name}","releases_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/releases{/id}","deployments_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/deployments","created_at":"2013-05-31T06:37:20Z","updated_at":"2016-07-19T12:45:42Z","pushed_at":"2016-08-17T15:40:24Z","git_url":"git://github.com/geoadmin/mf-geoadmin3.git","ssh_url":"git@github.com:geoadmin/mf-geoadmin3.git","clone_url":"https://github.com/geoadmin/mf-geoadmin3.git","svn_url":"https://github.com/geoadmin/mf-geoadmin3","homepage":"http://map.geo.admin.ch","size":260017,"stargazers_count":93,"watchers_count":93,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":47,"mirror_url":null,"open_issues_count":109,"forks":47,"open_issues":109,"watchers":93,"default_branch":"master"}},"base":{"label":"geoadmin:master","ref":"master","sha":"1194bae8b2f05f19a6d728d429d82575d8f86690","user":{"login":"geoadmin","id":3369793,"avatar_url":"https://avatars.githubusercontent.com/u/3369793?v=3","gravatar_id":"","url":"https://api.github.com/users/geoadmin","html_url":"https://github.com/geoadmin","followers_url":"https://api.github.com/users/geoadmin/followers","following_url":"https://api.github.com/users/geoadmin/following{/other_user}","gists_url":"https://api.github.com/users/geoadmin/gists{/gist_id}","starred_url":"https://api.github.com/users/geoadmin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/geoadmin/subscriptions","organizations_url":"https://api.github.com/users/geoadmin/orgs","repos_url":"https://api.github.com/users/geoadmin/repos","events_url":"https://api.github.com/users/geoadmin/events{/privacy}","received_events_url":"https://api.github.com/users/geoadmin/received_events","type":"Organization","site_admin":false},"repo":{"id":10399273,"name":"mf-geoadmin3","full_name":"geoadmin/mf-geoadmin3","owner":{"login":"geoadmin","id":3369793,"avatar_url":"https://avatars.githubusercontent.com/u/3369793?v=3","gravatar_id":"","url":"https://api.github.com/users/geoadmin","html_url":"https://github.com/geoadmin","followers_url":"https://api.github.com/users/geoadmin/followers","following_url":"https://api.github.com/users/geoadmin/following{/other_user}","gists_url":"https://api.github.com/users/geoadmin/gists{/gist_id}","starred_url":"https://api.github.com/users/geoadmin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/geoadmin/subscriptions","organizations_url":"https://api.github.com/users/geoadmin/orgs","repos_url":"https://api.github.com/users/geoadmin/repos","events_url":"https://api.github.com/users/geoadmin/events{/privacy}","received_events_url":"https://api.github.com/users/geoadmin/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/geoadmin/mf-geoadmin3","description":"map.geo.admin.ch source code","fork":false,"url":"https://api.github.com/repos/geoadmin/mf-geoadmin3","forks_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/forks","keys_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/keys{/key_id}","collaborators_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/teams","hooks_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/hooks","issue_events_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/issues/events{/number}","events_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/events","assignees_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/assignees{/user}","branches_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/branches{/branch}","tags_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/tags","blobs_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/git/refs{/sha}","trees_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/git/trees{/sha}","statuses_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/statuses/{sha}","languages_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/languages","stargazers_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/stargazers","contributors_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/contributors","subscribers_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/subscribers","subscription_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/subscription","commits_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/commits{/sha}","git_commits_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/git/commits{/sha}","comments_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/comments{/number}","issue_comment_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/issues/comments{/number}","contents_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/contents/{+path}","compare_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/compare/{base}...{head}","merges_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/merges","archive_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/downloads","issues_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/issues{/number}","pulls_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/pulls{/number}","milestones_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/milestones{/number}","notifications_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/labels{/name}","releases_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/releases{/id}","deployments_url":"https://api.github.com/repos/geoadmin/mf-geoadmin3/deployments","created_at":"2013-05-31T06:37:20Z","updated_at":"2016-07-19T12:45:42Z","pushed_at":"2016-08-17T15:40:24Z","git_url":"git://github.com/geoadmin/mf-geoadmin3.git","ssh_url":"git@github.com:geoadmin/mf-geoadmin3.git","clone_url":"https://github.com/geoadmin/mf-geoadmin3.git","svn_url":"https://github.com/geoadmin/mf-geoadmin3","homepage":"http://map.geo.admin.ch","size":260017,"stargazers_count":93,"watchers_count":93,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":47,"mirror_url":null,"open_issues_count":109,"forks":47,"open_issues":109,"watchers":93,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/geoadmin/mf-geoadmin3/pulls/3410"},"html":{"href":"https://github.com/geoadmin/mf-geoadmin3/pull/3410"},"issue":{"href":"https://api.github.com/repos/geoadmin/mf-geoadmin3/issues/3410"},"comments":{"href":"https://api.github.com/repos/geoadmin/mf-geoadmin3/issues/3410/comments"},"review_comments":{"href":"https://api.github.com/repos/geoadmin/mf-geoadmin3/pulls/3410/comments"},"review_comment":{"href":"https://api.github.com/repos/geoadmin/mf-geoadmin3/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/geoadmin/mf-geoadmin3/pulls/3410/commits"},"statuses":{"href":"https://api.github.com/repos/geoadmin/mf-geoadmin3/statuses/ca48331d711d93b6323d3d55f08a598d069abd9a"}}}}
{"actor":{"display_login":"gjn"}}