├── .dockerignore
├── .gitignore
├── .travis.yml
├── README.md
├── app
├── .htaccess
├── AppCache.php
├── AppKernel.php
├── Resources
│ ├── DoctrineMigrations
│ │ ├── Version20160106103017.php
│ │ ├── Version20160111143118.php
│ │ └── Version20160305162418.php
│ ├── FOSUserBundle
│ │ └── views
│ │ │ ├── Registration
│ │ │ └── register_content.html.twig
│ │ │ ├── Security
│ │ │ └── login.html.twig
│ │ │ └── layout.html.twig
│ ├── KnpPaginatorBundle
│ │ └── views
│ │ │ └── Pagination
│ │ │ └── twitter_bootstrap_v3_pagination.html.twig
│ ├── assets
│ │ ├── js
│ │ │ └── repository.js
│ │ └── scss
│ │ │ ├── main.scss
│ │ │ └── pager.scss
│ ├── broker
│ │ └── mapping.yml
│ ├── translations
│ │ └── messages.en.yml
│ └── views
│ │ ├── account
│ │ └── index.html.twig
│ │ ├── admin
│ │ ├── repository
│ │ │ └── index.html.twig
│ │ └── user
│ │ │ └── index.html.twig
│ │ ├── base.html.twig
│ │ ├── default
│ │ ├── index.html.twig
│ │ ├── last_public_repositories.html.twig
│ │ ├── most_pulled_repositories.html.twig
│ │ └── most_stared_repositories.html.twig
│ │ ├── layout.html.twig
│ │ ├── layout
│ │ ├── navbar.html.twig
│ │ └── notifications.html.twig
│ │ ├── macros.html.twig
│ │ ├── repository
│ │ ├── _edit.html.twig
│ │ ├── _view.html.twig
│ │ ├── index.html.twig
│ │ ├── layout.html.twig
│ │ └── webhooks.html.twig
│ │ └── search
│ │ └── results.html.twig
├── autoload.php
└── config
│ ├── config.yml
│ ├── config_dev.yml
│ ├── config_prod.yml
│ ├── config_test.yml
│ ├── fos_user.yml
│ ├── parameters.yml.dist
│ ├── routing.yml
│ ├── routing_dev.yml
│ ├── security.yml
│ ├── services.yml
│ └── swarrot.yml
├── behat.yml.dist
├── bin
├── console
├── run-tests
└── symfony_requirements
├── bower.json
├── composer.json
├── composer.lock
├── docker-compose.yml
├── docker
├── build
│ ├── Dockerfile
│ ├── Dockerfile-from-source
│ ├── README-short.md
│ ├── README.md
│ └── entrypoint.sh
├── docker-compose.nodejs.yml
└── elasticsearch
│ └── Dockerfile
├── features
├── account.feature
├── bootstrap
│ ├── AuthenticationContext.php
│ ├── EntityContext.php
│ └── RestContext.php
├── fixtures
│ ├── layers
│ │ ├── 03f4658f8b782e12230c1783426bd3bacce651ce582a4ffb6fbbfa2079428ecb
│ │ └── a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
│ └── manifests
│ │ ├── hello-world:latest.json
│ │ ├── secret-world:latest.json
│ │ ├── test~hello-world:latest.json
│ │ └── test~secret-world:latest.json
├── homepage.feature
├── registration.feature
├── registry
│ ├── access_manifest.feature
│ ├── token.feature
│ ├── upload_manifest.feature
│ └── version.feature
├── repository_description.feature
└── search.feature
├── gulpfile.js
├── package.json
├── phpunit.xml.dist
├── src
├── .htaccess
└── AppBundle
│ ├── AppBundle.php
│ ├── Broker
│ └── Processor
│ │ └── WebhookProcessor.php
│ ├── Command
│ └── SetupBrokerCommand.php
│ ├── Controller
│ ├── AccountController.php
│ ├── Admin
│ │ ├── RepositoryController.php
│ │ └── UserController.php
│ ├── DefaultController.php
│ ├── Registry
│ │ ├── LayerController.php
│ │ ├── ManifestController.php
│ │ ├── TokenController.php
│ │ └── VersionController.php
│ ├── RepositoryController.php
│ └── SearchController.php
│ ├── Entity
│ ├── Layer.php
│ ├── Manifest.php
│ ├── ManifestRepository.php
│ ├── Repository.php
│ ├── RepositoryRepository.php
│ ├── RepositoryStar.php
│ ├── RepositoryStarListener.php
│ ├── RepositoryStarRepository.php
│ ├── User.php
│ └── Webhook.php
│ ├── Event
│ ├── DelayedEvent.php
│ └── ManifestEvent.php
│ ├── EventListener
│ ├── DelayedEventListener.php
│ ├── HeaderResponseListener.php
│ ├── ManifestPullListener.php
│ ├── ManifestPushListener.php
│ └── RegistryExceptionListener.php
│ ├── Form
│ └── Type
│ │ ├── RepositoryType.php
│ │ └── WebhookType.php
│ ├── Manager
│ ├── LayerManager.php
│ ├── RepositoryStarManager.php
│ └── SearchManager.php
│ ├── Security
│ ├── RegistryEntryPoint.php
│ └── Voter
│ │ └── RepositoryVoter.php
│ └── Twig
│ └── AppExtension.php
├── var
├── SymfonyRequirements.php
├── cache
│ └── .gitkeep
├── jwt
│ ├── private.pem.dist
│ └── public.pem.dist
├── logs
│ └── .gitkeep
└── storage
│ └── .gitkeep
└── web
├── .htaccess
├── app.php
├── app_dev.php
├── apple-touch-icon.png
├── assets
├── css
│ └── main.css
├── fonts
│ ├── bootstrap
│ │ ├── glyphicons-halflings-regular.eot
│ │ ├── glyphicons-halflings-regular.svg
│ │ ├── glyphicons-halflings-regular.ttf
│ │ ├── glyphicons-halflings-regular.woff
│ │ └── glyphicons-halflings-regular.woff2
│ ├── glyphicons-halflings-regular.eot
│ ├── glyphicons-halflings-regular.svg
│ ├── glyphicons-halflings-regular.ttf
│ ├── glyphicons-halflings-regular.woff
│ └── glyphicons-halflings-regular.woff2
└── js
│ └── main.js
├── config.php
├── favicon.ico
└── robots.txt
/.dockerignore:
--------------------------------------------------------------------------------
1 | .*
2 | /app/config/parameters.yml
3 | /build/
4 | /phpunit.xml
5 | /var/*
6 | !/var/cache
7 | /var/cache/*
8 | !var/cache/.gitkeep
9 | !/var/logs
10 | /var/logs/*
11 | !var/logs/.gitkeep
12 | !/var/sessions
13 | /var/sessions/*
14 | !var/sessions/.gitkeep
15 | !var/storage
16 | /var/storage/*
17 | !var/storage/.gitkeep
18 | !var/jwt
19 | /var/jwt/*
20 | !var/jwt/*.dist
21 | !var/SymfonyRequirements.php
22 | /vendor/
23 | /web/bundles/
24 | /bin/*
25 | !/bin/run-tests
26 | !bin/console
27 | !bin/symfony_requirements
28 | /bower_components/
29 | /node_modules/
30 | /.sass-cache/
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /app/config/parameters.yml
2 | /build/
3 | /phpunit.xml
4 | /var/*
5 | !/var/cache
6 | /var/cache/*
7 | !var/cache/.gitkeep
8 | !/var/logs
9 | /var/logs/*
10 | !var/logs/.gitkeep
11 | !/var/sessions
12 | /var/sessions/*
13 | !var/sessions/.gitkeep
14 | !var/storage
15 | /var/storage/*
16 | !var/storage/.gitkeep
17 | !var/jwt
18 | /var/jwt/*
19 | !var/jwt/*.dist
20 | !var/SymfonyRequirements.php
21 | /vendor/
22 | /web/bundles/
23 | /bin/*
24 | !/bin/run-tests
25 | !bin/console
26 | !bin/symfony_requirements
27 | /bower_components/
28 | /node_modules/
29 | /.sass-cache/
30 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 5.5
5 | - 5.6
6 | - 7.0
7 | - hhvm
8 |
9 | services:
10 | - mysql
11 | - elasticsearch
12 |
13 | matrix:
14 | allow_failures:
15 | - php: hhvm
16 |
17 | env:
18 | global:
19 | - DK_DATABASE_HOST=127.0.0.1
20 | - DK_DATABASE_PASSWORD=null
21 | - DK_ELASTICSEARCH_HOST=127.0.0.1
22 | - DK_ELASTICSEARCH_PORT=9200
23 |
24 | before_script:
25 | - cp var/jwt/private.pem.dist var/jwt/private.pem
26 | - cp var/jwt/public.pem.dist var/jwt/public.pem
27 | - if [[ $TRAVIS_PHP_VERSION != hhvm ]]; then phpenv config-rm xdebug.ini; fi;
28 | - composer self-update
29 | - composer install --no-interaction
30 |
31 | script:
32 | - ./bin/run-tests
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Dunkerque
2 | =========
3 |
4 | [](https://travis-ci.org/iamluc/dunkerque) [](https://insight.sensiolabs.com/projects/8789214a-26f9-42b6-a98b-de4e3fd5ba8e)
5 |
6 | # About
7 |
8 | Docker hub & registry.
9 |
10 | Written in PHP with Symfony.
11 |
12 | **THIS PROJECT IS IN ALPHA STATE**
13 |
14 | # Docker image
15 |
16 | If you just want to use dunkerque, use the [docker image on the docker hub](https://hub.docker.com/r/iamluc/dunkerque/),
17 | and read the dedicated [README](https://github.com/iamluc/dunkerque/blob/master/docker/build/README.md)
18 |
19 | # Install
20 |
21 | Base
22 |
23 | ```sh
24 | # Clone repository
25 | git clone https://github.com/iamluc/dunkerque
26 |
27 | # Enter directory
28 | cd dunkerque
29 |
30 | # Run server (Adapt file `docker-compose.yml` to your needs)
31 | docker-compose up -d
32 |
33 | # Generate keys (Default passphrase is `DunkerqueIsOnFire`)
34 | docker-compose run --rm app openssl genrsa -out var/jwt/private.pem -aes256 4096
35 | docker-compose run --rm app openssl rsa -pubout -in var/jwt/private.pem -out var/jwt/public.pem
36 |
37 | # Install dependencies
38 | docker-compose run --rm app composer install
39 |
40 | # Initialize database
41 | docker-compose run --rm app bin/console doctrine:database:create --if-not-exists
42 | docker-compose run --rm app bin/console doctrine:migrations:migrate
43 |
44 | # Initialize search
45 | docker-compose run --rm app bin/console fos:elastica:populate
46 |
47 | # Create a user
48 | docker-compose run --rm app bin/console fos:user:create
49 | ```
50 |
51 | # Develop on Dunkerque
52 |
53 | ```sh
54 | # Install dev dependencies
55 | docker-compose -f docker/docker-compose.nodejs.yml run --rm nodejs npm install
56 |
57 | # Push (already existing) repository to Dunkerque
58 | docker push 127.0.0.1:8000/user/repo
59 |
60 | # Compile SASS and JS
61 | docker-compose -f docker/docker-compose.nodejs.yml run --rm nodejs gulp
62 |
63 | # Run tests
64 | docker-compose run --rm app bin/run-tests
65 | ```
66 |
67 | # LICENSE
68 |
69 | [MIT](https://opensource.org/licenses/MIT)
70 |
--------------------------------------------------------------------------------
/app/.htaccess:
--------------------------------------------------------------------------------
1 |
2 | Require all denied
3 |
4 |
5 | Order deny,allow
6 | Deny from all
7 |
8 |
--------------------------------------------------------------------------------
/app/AppCache.php:
--------------------------------------------------------------------------------
1 | getEnvironment(), ['dev', 'test'], true)) {
34 | $bundles[] = new Symfony\Bundle\DebugBundle\DebugBundle();
35 | $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
36 | $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();
37 | $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
38 | }
39 |
40 | return $bundles;
41 | }
42 |
43 | public function getRootDir()
44 | {
45 | return __DIR__;
46 | }
47 |
48 | public function getCacheDir()
49 | {
50 | return dirname(__DIR__).'/var/cache/'.$this->getEnvironment();
51 | }
52 |
53 | public function getLogDir()
54 | {
55 | return dirname(__DIR__).'/var/logs';
56 | }
57 |
58 | public function registerContainerConfiguration(LoaderInterface $loader)
59 | {
60 | $loader->load($this->getRootDir().'/config/config_'.$this->getEnvironment().'.yml');
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/app/Resources/DoctrineMigrations/Version20160106103017.php:
--------------------------------------------------------------------------------
1 | abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
20 |
21 | $this->addSql('CREATE TABLE webhook (id INT AUTO_INCREMENT NOT NULL, repository_id INT NOT NULL, name VARCHAR(50) NOT NULL, url VARCHAR(255) NOT NULL, last_call DATETIME DEFAULT NULL, last_status VARCHAR(50) DEFAULT NULL, INDEX IDX_8A74175650C9D4F7 (repository_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
22 | $this->addSql('CREATE TABLE repository (id INT AUTO_INCREMENT NOT NULL, owner_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, title VARCHAR(255) DEFAULT NULL, description LONGTEXT DEFAULT NULL, private TINYINT(1) NOT NULL, stars INT NOT NULL, pulls INT NOT NULL, INDEX IDX_5CFE57CD7E3C61F9 (owner_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
23 | $this->addSql('CREATE TABLE user (id INT AUTO_INCREMENT NOT NULL, username VARCHAR(255) NOT NULL, username_canonical VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, email_canonical VARCHAR(255) NOT NULL, enabled TINYINT(1) NOT NULL, salt VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL, last_login DATETIME DEFAULT NULL, locked TINYINT(1) NOT NULL, expired TINYINT(1) NOT NULL, expires_at DATETIME DEFAULT NULL, confirmation_token VARCHAR(255) DEFAULT NULL, password_requested_at DATETIME DEFAULT NULL, roles LONGTEXT NOT NULL COMMENT \'(DC2Type:array)\', credentials_expired TINYINT(1) NOT NULL, credentials_expire_at DATETIME DEFAULT NULL, UNIQUE INDEX UNIQ_8D93D64992FC23A8 (username_canonical), UNIQUE INDEX UNIQ_8D93D649A0D96FBF (email_canonical), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
24 | $this->addSql('CREATE TABLE layer (id INT AUTO_INCREMENT NOT NULL, uuid VARCHAR(255) NOT NULL, digest VARCHAR(255) DEFAULT NULL, status INT NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
25 | $this->addSql('CREATE TABLE manifest (id INT AUTO_INCREMENT NOT NULL, repository_id INT NOT NULL, tag VARCHAR(255) NOT NULL, digest VARCHAR(255) NOT NULL, content LONGTEXT NOT NULL, pulls INT NOT NULL, updated_at DATETIME NOT NULL, INDEX IDX_A6FA684050C9D4F7 (repository_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
26 | $this->addSql('ALTER TABLE webhook ADD CONSTRAINT FK_8A74175650C9D4F7 FOREIGN KEY (repository_id) REFERENCES repository (id)');
27 | $this->addSql('ALTER TABLE repository ADD CONSTRAINT FK_5CFE57CD7E3C61F9 FOREIGN KEY (owner_id) REFERENCES user (id)');
28 | $this->addSql('ALTER TABLE manifest ADD CONSTRAINT FK_A6FA684050C9D4F7 FOREIGN KEY (repository_id) REFERENCES repository (id)');
29 | }
30 |
31 | /**
32 | * @param Schema $schema
33 | */
34 | public function down(Schema $schema)
35 | {
36 | // this down() migration is auto-generated, please modify it to your needs
37 | $this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
38 |
39 | $this->addSql('ALTER TABLE webhook DROP FOREIGN KEY FK_8A74175650C9D4F7');
40 | $this->addSql('ALTER TABLE manifest DROP FOREIGN KEY FK_A6FA684050C9D4F7');
41 | $this->addSql('ALTER TABLE repository DROP FOREIGN KEY FK_5CFE57CD7E3C61F9');
42 | $this->addSql('DROP TABLE webhook');
43 | $this->addSql('DROP TABLE repository');
44 | $this->addSql('DROP TABLE user');
45 | $this->addSql('DROP TABLE layer');
46 | $this->addSql('DROP TABLE manifest');
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/Resources/DoctrineMigrations/Version20160111143118.php:
--------------------------------------------------------------------------------
1 | abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
20 |
21 | $this->addSql('CREATE TABLE repository_star (user_id INT NOT NULL, repository_id INT NOT NULL, INDEX IDX_7AF57DFCA76ED395 (user_id), INDEX IDX_7AF57DFC50C9D4F7 (repository_id), PRIMARY KEY(user_id, repository_id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
22 | $this->addSql('ALTER TABLE repository_star ADD CONSTRAINT FK_7AF57DFCA76ED395 FOREIGN KEY (user_id) REFERENCES user (id)');
23 | $this->addSql('ALTER TABLE repository_star ADD CONSTRAINT FK_7AF57DFC50C9D4F7 FOREIGN KEY (repository_id) REFERENCES repository (id)');
24 | }
25 |
26 | /**
27 | * @param Schema $schema
28 | */
29 | public function down(Schema $schema)
30 | {
31 | // this down() migration is auto-generated, please modify it to your needs
32 | $this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
33 |
34 | $this->addSql('DROP TABLE repository_star');
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/Resources/DoctrineMigrations/Version20160305162418.php:
--------------------------------------------------------------------------------
1 | abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
20 |
21 | $this->addSql('CREATE UNIQUE INDEX UNIQ_5CFE57CD5E237E06 ON repository (name)');
22 | }
23 |
24 | /**
25 | * @param Schema $schema
26 | */
27 | public function down(Schema $schema)
28 | {
29 | // this down() migration is auto-generated, please modify it to your needs
30 | $this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
31 |
32 | $this->addSql('DROP INDEX UNIQ_5CFE57CD5E237E06 ON repository');
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/Resources/FOSUserBundle/views/Registration/register_content.html.twig:
--------------------------------------------------------------------------------
1 | {% trans_default_domain 'FOSUserBundle' %}
2 |
3 | {{ form_start(form, {'method': 'post', 'action': path('fos_user_registration_register'), 'attr': {'class': 'fos_user_registration_register'}}) }}
4 | {{ form_widget(form) }}
5 |
6 |
7 |
8 | {{ form_end(form) }}
9 |
--------------------------------------------------------------------------------
/app/Resources/FOSUserBundle/views/Security/login.html.twig:
--------------------------------------------------------------------------------
1 | {% extends "FOSUserBundle::layout.html.twig" %}
2 |
3 | {% trans_default_domain 'FOSUserBundle' %}
4 |
5 | {% block fos_user_content %}
6 | {% if error %}
7 | {{ error.messageKey|trans(error.messageData, 'security') }}
8 | {% endif %}
9 |
10 |
32 | {% endblock fos_user_content %}
33 |
--------------------------------------------------------------------------------
/app/Resources/FOSUserBundle/views/layout.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'layout.html.twig' %}
2 |
3 | {% block content %}
4 | {% block fos_user_content %}{% endblock %}
5 | {% endblock %}
6 |
--------------------------------------------------------------------------------
/app/Resources/KnpPaginatorBundle/views/Pagination/twitter_bootstrap_v3_pagination.html.twig:
--------------------------------------------------------------------------------
1 | {% if pageCount > 1 %}
2 |
77 | {% endif %}
78 |
--------------------------------------------------------------------------------
/app/Resources/assets/js/repository.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 | $('#repository-star').click(function(e) {
3 | var link = $(this);
4 | var icon = link.find('.glyphicon');
5 |
6 | $.ajax(
7 | icon.hasClass('glyphicon-star') ? link.attr('data-url-unstar') : link.attr('data-url-star'),
8 | {method: 'PUT'}
9 | ).done(function(data) {
10 | if (data.starred) {
11 | icon.addClass('glyphicon-star').removeClass('glyphicon-star-empty');
12 | } else {
13 | icon.addClass('glyphicon-star-empty').removeClass('glyphicon-star');
14 | }
15 | });
16 | e.preventDefault();
17 | });
18 | })
19 |
--------------------------------------------------------------------------------
/app/Resources/assets/scss/main.scss:
--------------------------------------------------------------------------------
1 | @import "_bootstrap";
2 | @import "pager";
3 |
4 | .tabs-menu {
5 | margin-bottom: 20px;
6 | }
7 |
8 | .panel-body > ul {
9 | padding-left: 10px;
10 | }
11 |
12 | li {
13 | list-style: none;
14 | }
15 |
--------------------------------------------------------------------------------
/app/Resources/assets/scss/pager.scss:
--------------------------------------------------------------------------------
1 | .pager-filter-container {
2 | display: none;
3 | padding-top: 5px;
4 | margin-left: -15px;
5 | }
6 |
7 | input:focus ~ .pager-filter-container, .pager-filter-container:hover {
8 | display: block;
9 | z-index: 1000;
10 | position: absolute;
11 | padding-bottom: 160px; /* Large padding-bottom to block mouseout */
12 | }
13 |
14 | .pager-filter-container-body {
15 | position: relative;
16 | background-color: white;
17 | border: 1px solid #DDDDDD;
18 | padding: 20px 40px;
19 | -webkit-border-radius: 0px 0px 5px 5px;
20 | -moz-border-radius: 0px 0px 5px 5px;
21 | border-radius: 0px 0px 5px 5px;
22 | }
23 |
24 | .pager-filter-container-body:after, .pager-filter-container-body:before {
25 | bottom: 100%;
26 | border: solid transparent;
27 | content: " ";
28 | height: 0;
29 | width: 0;
30 | position: absolute;
31 | pointer-events: none;
32 | }
33 |
34 | .pager-filter-container-body:after {
35 | border-color: rgba(255, 255, 255, 0);
36 | border-bottom-color: #ffffff;
37 | border-width: 10px;
38 | left: 15%;
39 | margin-left: -10px;
40 | }
41 |
42 | .pager-filter-container-body:before {
43 | border-color: rgba(221, 221, 221, 0);
44 | border-bottom-color: #DDDDDD;
45 | border-width: 11px;
46 | left: 15%;
47 | margin-left: -11px;
48 | }
49 |
50 | .masked {
51 | position: absolute;
52 | left: -500px;
53 | }
--------------------------------------------------------------------------------
/app/Resources/broker/mapping.yml:
--------------------------------------------------------------------------------
1 | /:
2 | exchanges:
3 | dk.manifest:
4 | type: topic
5 | durable: true
6 |
7 | queues:
8 | dk.manifest_webhook:
9 | durable: true
10 | bindings:
11 | -
12 | exchange: dk.manifest
13 | routing_key: push
14 |
--------------------------------------------------------------------------------
/app/Resources/translations/messages.en.yml:
--------------------------------------------------------------------------------
1 | welcome_user: "Welcome %username%"
2 |
3 | welcome_dunkerque.title: "Welcome to Dunkerque"
4 | welcome_dunkerque.body: "Your new docker registry !"
5 |
6 | users: "Users"
7 | add: "Add"
8 | delete: "Delete"
9 |
10 | invalid_csrf_token: "Invalid token, please refresh your page"
11 |
12 | search.find: "Find..."
13 | search.results_found: "{0} No result found for \"%keyword%\"|{1} 1 result found for \"%keyword%\"|]1,Inf[ %count% results found for \"%keyword%\""
14 |
15 | sort._score: "Relevance"
16 | sort.pulls: "Pulls"
17 | sort.stars: "Stars"
18 |
19 | repositories: "Repositories"
20 | repositories.most_stared: "Most stared"
21 | repositories.most_pulled: "Most pulled"
22 | repositories.last_public: "Latest public"
23 |
24 | repository.name: "Name"
25 | repository.stars: "Stars"
26 | repository.pulls: "Pulls"
27 | repository.is_private: "Private"
28 | repository.private: "Private"
29 | repository.public: "Public"
30 | repository.description: "Description"
31 | repository.pull: "Pull"
32 | repository.tags: "Tags"
33 | repository.title: "Title"
34 | repository.webhooks: "Webhooks"
35 |
36 | webhook.name: "Name"
37 | webhook.url: "Url"
38 | webhook.add: "Add"
39 | webhook.last_call: "Last call"
40 | webhook.last_status: "Last status"
41 | webhook.confirm_remove: "Do you really want to remove this webhook ?"
42 | webhook.not_found_or_not_granted: "Webhook not found, or you cannot access it."
43 | webhook.removed: "Webhook removed"
44 | webhook.created: "Webhook created"
45 | webhook.list: "Existing webhooks"
46 |
--------------------------------------------------------------------------------
/app/Resources/views/account/index.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'layout.html.twig' %}
2 |
3 | {% import 'DatathekePagerBundle:Pager:bootstrap3.html.twig' as helper %}
4 | {% import 'macros.html.twig' as macros %}
5 |
6 | {% block content %}
7 | {{ 'repositories'|trans }}
8 |
9 |
38 | {% endblock %}
39 |
--------------------------------------------------------------------------------
/app/Resources/views/admin/repository/index.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'layout.html.twig' %}
2 |
3 | {% block content %}
4 | {{ 'repositories'|trans }}
5 | {{ datagrid_content(datagrid) }}
6 | {% endblock %}
7 |
8 | {% block stylesheets %}
9 | {{ parent() }}
10 | {{ datagrid_stylesheets(datagrid, {no_external: true}) }}
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/app/Resources/views/admin/user/index.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'layout.html.twig' %}
2 |
3 | {% block content %}
4 | {{ 'users'|trans }}
5 | {{ datagrid_content(datagrid) }}
6 | {% endblock %}
7 |
8 | {% block stylesheets %}
9 | {{ parent() }}
10 | {{ datagrid_stylesheets(datagrid, {no_externals: true}) }}
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/app/Resources/views/base.html.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {% block title %}Welcome!{% endblock %}
9 | {% block stylesheets %}{% endblock %}
10 |
11 |
12 |
13 | {% block body %}{% endblock %}
14 | {% block javascripts %}{% endblock %}
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/Resources/views/default/index.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'layout.html.twig' %}
2 |
3 | {% block content %}
4 |
5 |
6 |
7 |
{{ 'welcome_dunkerque.title'|trans }}
8 |
9 |
10 | {{ 'welcome_dunkerque.body'|trans }}
11 |
12 |
13 |
14 |
15 |
{{ render(controller('AppBundle:Default:lastPublicRepositories')) }}
16 |
{{ render(controller('AppBundle:Default:mostStaredRepositories')) }}
17 |
{{ render(controller('AppBundle:Default:mostPulledRepositories')) }}
18 |
19 |
20 | {% endblock %}
21 |
--------------------------------------------------------------------------------
/app/Resources/views/default/last_public_repositories.html.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ 'repositories.last_public'|trans }}
4 |
5 |
6 |
7 | {% for repo in repositories %}
8 | - {{ repo.name }}
9 | {% endfor %}
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/Resources/views/default/most_pulled_repositories.html.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ 'repositories.most_pulled'|trans }}
4 |
5 |
6 |
7 | {% for repo in repositories %}
8 | -
9 | {{ repo.name }}
10 | {{ repo.pulls }}
11 |
12 | {% endfor %}
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/Resources/views/default/most_stared_repositories.html.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ 'repositories.most_stared'|trans }}
4 |
5 |
6 |
7 | {% for repo in repositories %}
8 | -
9 | {{ repo.name }}
10 | {{ repo.stars }}
11 |
12 | {% endfor %}
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/Resources/views/layout.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'base.html.twig' %}
2 |
3 | {% block body %}
4 | {{ include('layout/navbar.html.twig') }}
5 |
6 |
7 |
8 | {{ include('layout/notifications.html.twig') }}
9 |
10 | {% block content %}
11 | {% block fos_user_content %}{% endblock %}
12 | {% endblock %}
13 |
14 |
15 | {% endblock %}
16 |
17 | {% block stylesheets %}
18 |
19 | {% endblock %}
20 |
21 | {% block javascripts %}
22 |
23 | {% endblock %}
24 |
--------------------------------------------------------------------------------
/app/Resources/views/layout/navbar.html.twig:
--------------------------------------------------------------------------------
1 |
72 |
--------------------------------------------------------------------------------
/app/Resources/views/layout/notifications.html.twig:
--------------------------------------------------------------------------------
1 | {% for message in app.session.flashbag.get('error') %}
2 |
3 |
4 | {{ message|trans }}
5 |
6 | {% endfor %}
7 |
8 | {% for message in app.session.flashbag.get('success') %}
9 |
10 |
11 | {{ message|trans }}
12 |
13 | {% endfor %}
14 |
15 | {% for message in app.session.flashbag.get('info') %}
16 |
17 |
18 | {{ message|trans }}
19 |
20 | {% endfor %}
21 |
--------------------------------------------------------------------------------
/app/Resources/views/macros.html.twig:
--------------------------------------------------------------------------------
1 | {% macro repository_status(repository) %}
2 | {% if repository.private %}
3 |
4 | {{ 'repository.private'|trans }}
5 |
6 |
7 | {% else %}
8 |
9 | {{ 'repository.public'|trans }}
10 |
11 |
12 | {% endif %}
13 | {% endmacro %}
14 |
15 | {% macro repository_star(repository) %}
16 | {% set data_url_star = path('repository_star', {'name': repository.name, 'action': 'star' }) %}
17 | {% set data_url_unstar = path('repository_star', {'name': repository.name, 'action': 'unstar' }) %}
18 |
19 |
20 |
21 | {% endmacro %}
22 |
23 | {% macro repository_pull_count(repository) %}
24 |
25 | {{ repository.pulls }}
26 |
27 | {% endmacro %}
28 |
29 | {% macro repository_star_count(repository) %}
30 |
31 | {{ repository.stars }}
32 |
33 | {% endmacro %}
34 |
--------------------------------------------------------------------------------
/app/Resources/views/repository/_edit.html.twig:
--------------------------------------------------------------------------------
1 | {{ form_start(form) }}
2 | {{ form_widget(form) }}
3 |
4 |
5 | {{ form_end(form) }}
6 |
--------------------------------------------------------------------------------
/app/Resources/views/repository/_view.html.twig:
--------------------------------------------------------------------------------
1 | {{ 'repository.title'|trans }}
2 |
3 |
4 | {{ repository.title|default(repository.name) }}
5 |
6 |
7 | {{ 'repository.description'|trans }}
8 |
9 |
10 | {{ repository.description|default('-') }}
11 |
12 |
--------------------------------------------------------------------------------
/app/Resources/views/repository/index.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'repository/layout.html.twig' %}
2 |
3 | {% set active_tab = 'description' %}
4 |
5 | {% block tab_content %}
6 |
7 | {% if is_granted('REPO_WRITE', repository) %}
8 | {{ include('repository/_edit.html.twig') }}
9 | {% else %}
10 | {{ include('repository/_view.html.twig') }}
11 | {% endif %}
12 |
13 |
14 |
{{ 'repository.pull'|trans }}
15 |
16 |
17 |
18 |
19 |
20 |
{{ 'repository.tags'|trans }}
21 |
22 |
32 |
33 | {% endblock %}
34 |
--------------------------------------------------------------------------------
/app/Resources/views/repository/layout.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'layout.html.twig' %}
2 |
3 | {% import 'macros.html.twig' as macros %}
4 |
5 | {% block content %}
6 |
7 | {{ macros.repository_status(repository) }}
8 |
9 |
10 |
11 | - {{ repository.owner.username }}
12 | -
13 | {{ repository.name }}
14 | {% if is_granted('ROLE_USER') %}
15 | {{ macros.repository_star(repository) }}
16 | {% endif %}
17 |
18 |
19 |
20 |
28 |
29 |
30 |
31 | {% block tab_content %}{% endblock %}
32 |
33 |
34 | {% endblock %}
35 |
--------------------------------------------------------------------------------
/app/Resources/views/repository/webhooks.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'repository/layout.html.twig' %}
2 |
3 | {% set active_tab = 'webhooks' %}
4 |
5 | {% import 'DatathekePagerBundle:Pager:bootstrap3.html.twig' as helper %}
6 |
7 | {% block tab_content %}
8 | {{ 'webhook.list'|trans }}
9 |
10 |
44 |
45 | {{ 'webhook.add'|trans }}
46 |
47 | {{ form_start(form) }}
48 | {{ form_widget(form) }}
49 |
50 | {{ form_end(form) }}
51 | {% endblock %}
52 |
--------------------------------------------------------------------------------
/app/Resources/views/search/results.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'layout.html.twig' %}
2 |
3 | {% import 'DatathekePagerBundle:Pager:bootstrap3.html.twig' as helper %}
4 | {% import 'macros.html.twig' as macros %}
5 |
6 | {% block content %}
7 |
8 | {{ 'search.results_found'|transchoice(pager.totalItemCount, { '%count%': pager.totalItemCount, '%keyword%': keyword }) }}
9 |
10 |
11 | {% if pager.totalItemCount %}
12 |
41 | {% endif %}
42 | {% endblock %}
43 |
--------------------------------------------------------------------------------
/app/autoload.php:
--------------------------------------------------------------------------------
1 | getParameterOption(['--env', '-e'], getenv('SYMFONY_ENV') ?: 'dev');
21 | $debug = getenv('SYMFONY_DEBUG') !== '0' && !$input->hasParameterOption(['--no-debug', '']) && $env !== 'prod';
22 |
23 | if ($debug) {
24 | Debug::enable();
25 | }
26 |
27 | $kernel = new AppKernel($env, $debug);
28 | $application = new Application($kernel);
29 | $application->run($input);
30 |
--------------------------------------------------------------------------------
/bin/run-tests:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -e
3 |
4 | export SYMFONY_ENV=test
5 |
6 | if [ ! -f var/jwt/private.pem ]; then
7 | cp var/jwt/private.pem.dist var/jwt/private.pem
8 | cp var/jwt/public.pem.dist var/jwt/public.pem
9 | fi
10 |
11 | rm -rf var/cache/test
12 | bin/console doctrine:database:create --no-interaction --if-not-exists
13 | bin/console doctrine:schema:drop --no-interaction --full-database --force
14 | bin/console doctrine:migrations:migrate --no-interaction
15 |
16 | bin/behat $@
17 |
--------------------------------------------------------------------------------
/bin/symfony_requirements:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | getPhpIniConfigPath();
9 |
10 | echo_title('Symfony Requirements Checker');
11 |
12 | echo '> PHP is using the following php.ini file:'.PHP_EOL;
13 | if ($iniPath) {
14 | echo_style('green', ' '.$iniPath);
15 | } else {
16 | echo_style('warning', ' WARNING: No configuration file (php.ini) used by PHP!');
17 | }
18 |
19 | echo PHP_EOL.PHP_EOL;
20 |
21 | echo '> Checking Symfony requirements:'.PHP_EOL.' ';
22 |
23 | $messages = array();
24 | foreach ($symfonyRequirements->getRequirements() as $req) {
25 | /** @var $req Requirement */
26 | if ($helpText = get_error_message($req, $lineSize)) {
27 | echo_style('red', 'E');
28 | $messages['error'][] = $helpText;
29 | } else {
30 | echo_style('green', '.');
31 | }
32 | }
33 |
34 | $checkPassed = empty($messages['error']);
35 |
36 | foreach ($symfonyRequirements->getRecommendations() as $req) {
37 | if ($helpText = get_error_message($req, $lineSize)) {
38 | echo_style('yellow', 'W');
39 | $messages['warning'][] = $helpText;
40 | } else {
41 | echo_style('green', '.');
42 | }
43 | }
44 |
45 | if ($checkPassed) {
46 | echo_block('success', 'OK', 'Your system is ready to run Symfony projects');
47 | } else {
48 | echo_block('error', 'ERROR', 'Your system is not ready to run Symfony projects');
49 |
50 | echo_title('Fix the following mandatory requirements', 'red');
51 |
52 | foreach ($messages['error'] as $helpText) {
53 | echo ' * '.$helpText.PHP_EOL;
54 | }
55 | }
56 |
57 | if (!empty($messages['warning'])) {
58 | echo_title('Optional recommendations to improve your setup', 'yellow');
59 |
60 | foreach ($messages['warning'] as $helpText) {
61 | echo ' * '.$helpText.PHP_EOL;
62 | }
63 | }
64 |
65 | echo PHP_EOL;
66 | echo_style('title', 'Note');
67 | echo ' The command console could use a different php.ini file'.PHP_EOL;
68 | echo_style('title', '~~~~');
69 | echo ' than the one used with your web server. To be on the'.PHP_EOL;
70 | echo ' safe side, please check the requirements from your web'.PHP_EOL;
71 | echo ' server using the ';
72 | echo_style('yellow', 'web/config.php');
73 | echo ' script.'.PHP_EOL;
74 | echo PHP_EOL;
75 |
76 | exit($checkPassed ? 0 : 1);
77 |
78 | function get_error_message(Requirement $requirement, $lineSize)
79 | {
80 | if ($requirement->isFulfilled()) {
81 | return;
82 | }
83 |
84 | $errorMessage = wordwrap($requirement->getTestMessage(), $lineSize - 3, PHP_EOL.' ').PHP_EOL;
85 | $errorMessage .= ' > '.wordwrap($requirement->getHelpText(), $lineSize - 5, PHP_EOL.' > ').PHP_EOL;
86 |
87 | return $errorMessage;
88 | }
89 |
90 | function echo_title($title, $style = null)
91 | {
92 | $style = $style ?: 'title';
93 |
94 | echo PHP_EOL;
95 | echo_style($style, $title.PHP_EOL);
96 | echo_style($style, str_repeat('~', strlen($title)).PHP_EOL);
97 | echo PHP_EOL;
98 | }
99 |
100 | function echo_style($style, $message)
101 | {
102 | // ANSI color codes
103 | $styles = array(
104 | 'reset' => "\033[0m",
105 | 'red' => "\033[31m",
106 | 'green' => "\033[32m",
107 | 'yellow' => "\033[33m",
108 | 'error' => "\033[37;41m",
109 | 'success' => "\033[37;42m",
110 | 'title' => "\033[34m",
111 | );
112 | $supports = has_color_support();
113 |
114 | echo($supports ? $styles[$style] : '').$message.($supports ? $styles['reset'] : '');
115 | }
116 |
117 | function echo_block($style, $title, $message)
118 | {
119 | $message = ' '.trim($message).' ';
120 | $width = strlen($message);
121 |
122 | echo PHP_EOL.PHP_EOL;
123 |
124 | echo_style($style, str_repeat(' ', $width).PHP_EOL);
125 | echo_style($style, str_pad(' ['.$title.']', $width, ' ', STR_PAD_RIGHT).PHP_EOL);
126 | echo_style($style, str_pad($message, $width, ' ', STR_PAD_RIGHT).PHP_EOL);
127 | echo_style($style, str_repeat(' ', $width).PHP_EOL);
128 | }
129 |
130 | function has_color_support()
131 | {
132 | static $support;
133 |
134 | if (null === $support) {
135 | if (DIRECTORY_SEPARATOR == '\\') {
136 | $support = false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI');
137 | } else {
138 | $support = function_exists('posix_isatty') && @posix_isatty(STDOUT);
139 | }
140 | }
141 |
142 | return $support;
143 | }
144 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "iamluc/dunkerque",
3 | "dependencies": {
4 | "bootstrap-sass-official": "~3.3.0"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "iamluc/dunkerque",
3 | "license": "MIT",
4 | "type": "project",
5 | "description": "Docker hub & registry",
6 | "keywords": ["docker", "hub", "registry"],
7 | "authors": [
8 | {
9 | "name": "Luc Vieillescazes",
10 | "email": "luc@vieillescazes.net"
11 | }
12 | ],
13 | "autoload": {
14 | "psr-4": { "": "src/" },
15 | "classmap": [ "app/AppKernel.php", "app/AppCache.php" ]
16 | },
17 | "autoload-dev": {
18 | "psr-4": { "Tests\\": "tests/" }
19 | },
20 | "repositories": [
21 | {
22 | "type": "vcs",
23 | "url": "https://github.com/iamluc/rabbit-mq-admin-toolkit"
24 | }
25 | ],
26 | "require": {
27 | "php": ">=5.5.9",
28 | "symfony/symfony": "3.0.*",
29 | "doctrine/orm": "^2.5",
30 | "doctrine/doctrine-bundle": "^1.6",
31 | "doctrine/doctrine-cache-bundle": "^1.2",
32 | "symfony/swiftmailer-bundle": "^2.3",
33 | "symfony/monolog-bundle": "^2.8",
34 | "sensio/distribution-bundle": "^5.0",
35 | "sensio/framework-extra-bundle": "^3.0.2",
36 | "incenteev/composer-parameter-handler": "^2.0",
37 | "ramsey/uuid": "^2.8",
38 | "friendsofsymfony/user-bundle": "~2.0@dev",
39 | "friendsofsymfony/elastica-bundle": "dev-master",
40 | "datatheke/pager-bundle": "^0.5.2",
41 | "swarrot/swarrot-bundle": "^1.3",
42 | "odolbeau/rabbit-mq-admin-toolkit": "dev-symfony3",
43 | "php-amqplib/php-amqplib": "^2.6",
44 | "doctrine/doctrine-migrations-bundle": "^1.1",
45 | "lexik/jwt-authentication-bundle": "^1.3",
46 | "oneup/flysystem-bundle": "^1.2"
47 | },
48 | "require-dev": {
49 | "sensio/generator-bundle": "^3.0",
50 | "symfony/phpunit-bridge": "^3.0",
51 | "behat/behat": "~3.1@dev",
52 | "behat/symfony2-extension": "^2.0",
53 | "behat/mink-extension": "^2.0",
54 | "behat/mink-browserkit-driver": "^1.2",
55 | "knplabs/friendly-contexts": "^0.7",
56 | "behatch/contexts": "dev-master",
57 | "behat/mink-goutte-driver": "^1.2"
58 | },
59 | "scripts": {
60 | "post-root-package-install": [
61 | "SymfonyStandard\\Composer::hookRootPackageInstall"
62 | ],
63 | "post-install-cmd": [
64 | "Incenteev\\ParameterHandler\\ScriptHandler::buildParameters",
65 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap",
66 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::clearCache",
67 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installAssets",
68 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installRequirementsFile",
69 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::prepareDeploymentTarget"
70 | ],
71 | "post-update-cmd": [
72 | "Incenteev\\ParameterHandler\\ScriptHandler::buildParameters",
73 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap",
74 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::clearCache",
75 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installAssets",
76 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installRequirementsFile",
77 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::prepareDeploymentTarget"
78 | ]
79 | },
80 | "config": {
81 | "bin-dir": "bin"
82 | },
83 | "extra": {
84 | "symfony-app-dir": "app",
85 | "symfony-bin-dir": "bin",
86 | "symfony-var-dir": "var",
87 | "symfony-web-dir": "web",
88 | "symfony-tests-dir": "tests",
89 | "symfony-assets-install": "relative",
90 | "incenteev-parameters": {
91 | "file": "app/config/parameters.yml",
92 | "env-map": {
93 | "storage_path": "DK_STORAGE_PATH",
94 | "secret": "DK_SECRET",
95 | "database_host": "DK_DATABASE_HOST",
96 | "database_port": "DK_DATABASE_PORT",
97 | "database_name": "DK_DATABASE_NAME",
98 | "database_user": "DK_DATABASE_USER",
99 | "database_password": "DK_DATABASE_PASSWORD",
100 | "rabbitmq_host": "DK_RABBITMQ_HOST",
101 | "rabbitmq_port": "DK_RABBITMQ_PORT",
102 | "rabbitmq_login": "DK_RABBITMQ_LOGIN",
103 | "rabbitmq_password": "DK_RABBITMQ_PASSWORD",
104 | "jwt_key_pass_phrase": "DK_JWT_KEY_PASS_PHRASE",
105 | "trusted_proxies": "DK_TRUSTED_PROXIES",
106 | "elasticsearch_host": "DK_ELASTICSEARCH_HOST",
107 | "elasticsearch_port": "DK_ELASTICSEARCH_PORT"
108 | }
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | app:
2 | image: iamluc/symfony
3 | container_name: dunkerque
4 | volumes:
5 | - .:/var/www/html
6 | # To speed up composer
7 | # - ~/.composer:/var/www/.composer
8 |
9 | environment:
10 | - DOCKER_ENV=dev
11 | ports:
12 | # Adapt to you need
13 | - 8000:80
14 |
15 | links:
16 | - mariadb:db
17 | - rabbitmq:rabbitmq
18 | - elasticsearch:elasticsearch
19 |
20 | # Uncomment lines below to enable Blackfire
21 | # You have to export env variables (BLACKFIRE_SERVER_ID and BLACKFIRE_SERVER_TOKEN) before running docker-compose
22 | # - blackfire:blackfire
23 | #
24 | #blackfire:
25 | # image: blackfire/blackfire
26 | # environment:
27 | # - BLACKFIRE_SERVER_ID
28 | # - BLACKFIRE_SERVER_TOKEN
29 |
30 |
31 | phpmyadmin:
32 | image: phpmyadmin/phpmyadmin
33 | container_name: dunkerque_phpmyadmin
34 | links:
35 | - mariadb:db
36 |
37 | mariadb:
38 | image: mariadb:10
39 | environment:
40 | - MYSQL_ROOT_PASSWORD=dkpassword
41 |
42 | rabbitmq:
43 | image: rabbitmq:3-management
44 | container_name: dunkerque_rabbitmq
45 | environment:
46 | - RABBITMQ_DEFAULT_USER=dunkerque
47 | - RABBITMQ_DEFAULT_PASS=dkpassword
48 | ports:
49 | - 15672:15672
50 |
51 | workerwebhook:
52 | image: iamluc/symfony
53 | volumes_from:
54 | - app
55 | links:
56 | - rabbitmq:rabbitmq
57 | - mariadb:db
58 | environment:
59 | - SYMFONY_ENV=dev
60 | command: sleep 5 && bin/console dunkerque:broker:setup && bin/console swarrot:consume:webhook
61 |
62 | elasticsearch:
63 | image: elasticsearch:1
64 | container_name: dunkerque_elasticsearch
65 | ports:
66 | - 9200:9200
67 |
--------------------------------------------------------------------------------
/docker/build/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM iamluc/symfony
2 |
3 | MAINTAINER Luc Vieillescazes
4 |
5 | RUN rm index.html \
6 | && wget -O - https://github.com/iamluc/dunkerque/archive/master.tar.gz | tar xz --strip 1 \
7 | && composer install --no-interaction --no-dev --no-scripts \
8 | && rm -Rf var/cache/* var/logs/* .composer web/app_dev.php web/config.php \
9 | && mkdir /data \
10 | && chown www-data:www-data var/cache var/logs /data
11 |
12 | COPY entrypoint.sh /usr/local/bin/entrypoint.sh
13 |
14 | VOLUME ["/data"]
15 |
16 | ENV SYMFONY_ENV prod
17 | ENV DK_STORAGE_PATH /data
18 | ENV DK_JWT_KEY_PASS_PHRASE DunkerqueIsOnFire
19 |
--------------------------------------------------------------------------------
/docker/build/Dockerfile-from-source:
--------------------------------------------------------------------------------
1 | FROM iamluc/symfony
2 |
3 | MAINTAINER Luc Vieillescazes
4 |
5 | COPY . ./
6 |
7 | RUN rm index.html \
8 | && composer install --no-interaction --no-dev --no-scripts \
9 | && rm -Rf var/cache/* var/logs/* .composer web/app_dev.php web/config.php \
10 | && mkdir /data \
11 | && chown www-data:www-data var/cache var/logs /data
12 |
13 | COPY docker/build/entrypoint.sh /usr/local/bin/entrypoint.sh
14 |
15 | VOLUME ["/data"]
16 |
17 | ENV SYMFONY_ENV prod
18 | ENV DK_STORAGE_PATH /data
19 | ENV DK_JWT_KEY_PASS_PHRASE DunkerqueIsOnFire
20 |
--------------------------------------------------------------------------------
/docker/build/README-short.md:
--------------------------------------------------------------------------------
1 | Docker hub & registry written in PHP with Symfony.
2 |
--------------------------------------------------------------------------------
/docker/build/README.md:
--------------------------------------------------------------------------------
1 | ### About
2 |
3 | Docker hub & registry written in PHP with Symfony.
4 |
5 | Souce code: [https://github.com/iamluc/dunkerque](https://github.com/iamluc/dunkerque)
6 | Docker image: [https://hub.docker.com/r/iamluc/dunkerque/](https://hub.docker.com/r/iamluc/dunkerque/)
7 |
8 | ### Run dunkerque
9 |
10 | Run the containers stack with a similar `docker-compose.yml` file:
11 |
12 | ```yml
13 | app:
14 | image: iamluc/dunkerque
15 | ports:
16 | - 80:80
17 | links:
18 | - mariadb:db
19 | - rabbitmq:rabbitmq
20 | - elasticsearch:elasticsearch
21 |
22 | mariadb:
23 | image: mariadb:10
24 | environment:
25 | - MYSQL_ROOT_PASSWORD=dkpassword
26 |
27 | elasticsearch:
28 | image: elasticsearch:1
29 |
30 | rabbitmq:
31 | image: rabbitmq:3-management
32 | environment:
33 | - RABBITMQ_DEFAULT_USER=dunkerque
34 | - RABBITMQ_DEFAULT_PASS=dkpassword
35 |
36 | workerwebhook:
37 | image: iamluc/dunkerque
38 | volumes_from:
39 | - app
40 | links:
41 | - mariadb:db
42 | - rabbitmq:rabbitmq
43 | command: sleep 5 && bin/console dunkerque:broker:setup && bin/console swarrot:consume:webhook
44 | ```
45 |
46 | ### Update
47 |
48 | To update, check that your custom `docker-compose.yml` is up-to-date.
49 | Then run
50 |
51 | ```
52 | # Download new images
53 | docker-compose pull
54 |
55 | # Recreate containers
56 | docker-compose up -d
57 |
58 | # Repopulate the elasticsearch index (used for the search)
59 | docker-compose run --rm app bin/console fos:elastica:populate
60 | ```
61 |
62 | ### Use your registry with docker
63 |
64 | Please note that currently the image exposes only port 80.
65 | You must setup a proxy (like [nginx-proxy](https://hub.docker.com/r/jwilder/nginx-proxy/)) with a certificate to use HTTPS.
66 | Without HTTPS, you must add the `--insecure-registry` option to your daemon configuration. See https://docs.docker.com/registry/insecure/.
67 |
68 | To push an image, you can follow this tutorial: https://www.digitalocean.com/community/tutorials/how-to-set-up-a-private-docker-registry-on-ubuntu-14-04#step-seven-—-publish-to-your-docker-registry
69 |
70 | ### Configure
71 |
72 | You can use environment variables to configure the `iamluc/dunkerque` image:
73 |
74 | | Variable name | Default value |
75 | |-------------------------|---------------------|
76 | | DK_STORAGE_PATH | /data |
77 |
--------------------------------------------------------------------------------
/docker/build/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | # Generate keys needed by JWT
5 | if [ ! -f var/jwt/private.pem ]; then
6 | openssl genrsa -passout env:DK_JWT_KEY_PASS_PHRASE -out var/jwt/private.pem -aes256 4096
7 | openssl rsa -pubout -passin env:DK_JWT_KEY_PASS_PHRASE -in var/jwt/private.pem -out var/jwt/public.pem
8 | fi
9 |
10 | # Init project with environment variables given at runtime
11 | composer run-script post-install-cmd --no-interaction --no-dev
12 |
13 | # Delete cache created by root
14 | rm -rf var/cache/* var/logs/*
15 |
16 | if [ "$1" = 'apache2ctl' ]; then
17 | # Let's time to other containers (i.e. mysql)
18 | sleep 5
19 |
20 | # Warmup cache
21 | su www-data -s /bin/bash -c "bin/console cache:warmup --no-interaction"
22 |
23 | # Setup/update database
24 | su www-data -s /bin/bash -c "bin/console doctrine:database:create --no-interaction --if-not-exists"
25 | su www-data -s /bin/bash -c "bin/console doctrine:migrations:migrate --no-interaction"
26 |
27 | # let's start apache as root
28 | exec "$@"
29 | else
30 | # change to user www-data
31 | su www-data -s /bin/bash -c "$*"
32 | fi
33 |
--------------------------------------------------------------------------------
/docker/docker-compose.nodejs.yml:
--------------------------------------------------------------------------------
1 | nodejs:
2 | image: jeanberu/ggbcls
3 | volumes:
4 | - ..:/src
5 |
--------------------------------------------------------------------------------
/docker/elasticsearch/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM elasticsearch:1
2 |
3 | RUN plugin -install mobz/elasticsearch-head
4 |
5 | EXPOSE 9200
6 | EXPOSE 9300
7 |
--------------------------------------------------------------------------------
/features/account.feature:
--------------------------------------------------------------------------------
1 | Feature: Test account
2 |
3 | @reset-schema
4 | Scenario: Fake background (for performance)
5 | Given I have users:
6 | | username | password | roles |
7 | | admin | admin | ROLE_ADMIN |
8 | | test | test | ROLE_USER |
9 | And the following repositories:
10 | | name | owner | private | stars | pulls |
11 | | hello-world | admin | false | 3 | 12 |
12 | And I have manifests:
13 | | hello-world:latest.json |
14 |
15 | Scenario: I go to a non-existing account
16 | Given I go to "/u/john"
17 | Then the response status code should be 404
18 |
19 | Scenario: I go to a existing account
20 | Given I go to "/u/admin"
21 | Then the response status code should be 200
22 | And I should see a table with 1 row
23 | And I should see the following table:
24 | | Name | Stars | Pulls | Private |
25 | | hello-world | 3 | 12 | Public |
26 |
27 | Scenario: I am authenticated
28 | Given I am authenticated as "test"
29 | And I go to "/"
30 | Then the response status code should be 200
31 | And I should see "test"
32 | And I should see "Log out"
33 |
--------------------------------------------------------------------------------
/features/bootstrap/AuthenticationContext.php:
--------------------------------------------------------------------------------
1 | getEnvironment();
29 | $this->minkContext = $environment->getContext('Behat\MinkExtension\Context\MinkContext');
30 | }
31 |
32 | /**
33 | * @Given I am authenticated as :username
34 | */
35 | public function iAmAuthenticatedAs($username)
36 | {
37 | /** @var \Behat\Mink\Session $session */
38 | $minkSession = $this->minkContext->getSession();
39 |
40 | /** @var \Behat\Symfony2Extension\Driver\KernelDriver $driver */
41 | $driver = $minkSession->getDriver();
42 | if (!$driver instanceof BrowserKitDriver) {
43 | throw new UnsupportedDriverActionException('This step is only supported by the BrowserKitDriver', $driver);
44 | }
45 |
46 | /** @var \Symfony\Component\Security\Core\User\UserProviderInterface $userProvider */
47 | $userProvider = $this->getContainer()->get('fos_user.user_provider.username');
48 | $user = $userProvider->loadUserByUsername($username);
49 |
50 | $token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
51 |
52 | $client = $driver->getClient();
53 | $session = $client->getContainer()->get('session');
54 | $session->set('_security_main', serialize($token));
55 | $session->save();
56 |
57 | $cookie = new Cookie($session->getName(), $session->getId());
58 | $client->getCookieJar()->set($cookie);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/features/bootstrap/EntityContext.php:
--------------------------------------------------------------------------------
1 | resolveEntity('users')->getName();
20 | $encoder = $this->getContainer()->get('security.password_encoder');
21 |
22 | $rows = $table->getRows();
23 | $headers = array_shift($rows);
24 |
25 | foreach ($rows as $row) {
26 | $values = array_combine($headers, $row);
27 | $entity = new $entityName;
28 | $reflection = new \ReflectionClass($entity);
29 |
30 | // Encode password
31 | $values['password'] = $encoder->encodePassword($entity, $values['password']);
32 | $values['enabled'] = true;
33 |
34 | do {
35 | $this
36 | ->getRecordBag()
37 | ->getCollection($reflection->getName())
38 | ->attach($entity, $values);
39 | $reflection = $reflection->getParentClass();
40 | } while (false !== $reflection);
41 |
42 | $this
43 | ->getEntityHydrator()
44 | ->hydrate($this->getEntityManager(), $entity, $values)
45 | ->completeRequired($this->getEntityManager(), $entity);
46 |
47 | $this->getEntityManager()->persist($entity);
48 | }
49 |
50 | $this->getEntityManager()->flush();
51 | }
52 |
53 | /**
54 | * @Given I have manifests:
55 | */
56 | public function iHaveManifests(TableNode $table)
57 | {
58 | $manifestsPath = __DIR__.'/../fixtures/manifests/';
59 | $em = $this->getEntityManager();
60 |
61 | foreach ($table->getRows() as $row) {
62 |
63 | $content = file_get_contents($manifestsPath.$row[0]);
64 | $repository = $em->getRepository('AppBundle:Repository')->findOneByName(json_decode($content, true)['name']);
65 |
66 | $manifest = new Manifest($repository);
67 | $manifest->setContent($content);
68 |
69 | $em->persist($manifest);
70 | }
71 |
72 | $em->flush();
73 | }
74 |
75 | /**
76 | * @Given Entities are indexed
77 | */
78 | public function entitiesAreIndexed()
79 | {
80 | $process = new Process('bin/console fos:elastica:populate');
81 | $process->run();
82 | if (!$process->isSuccessful()) {
83 | throw new \RuntimeException('Unable to populate elasticsearch');
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/features/bootstrap/RestContext.php:
--------------------------------------------------------------------------------
1 | request = $request;
19 |
20 | parent::__construct($request);
21 | }
22 |
23 | /**
24 | * @BeforeScenario @fixtures
25 | */
26 | public function loadFixtures(BeforeScenarioScope $scope)
27 | {
28 | $this->headers = [];
29 | $this->iAddHeaderEqualTo('PHP_AUTH_USER', null);
30 | $this->iAddHeaderEqualTo('PHP_AUTH_PW', null);
31 | }
32 |
33 | /**
34 | * @BeforeScenario
35 | */
36 | public function beforeScenario(BeforeScenarioScope $scope)
37 | {
38 | $this->variables = [];
39 | }
40 |
41 | /**
42 | * @AfterScenario
43 | */
44 | public function afterScenario(AfterScenarioScope $scope)
45 | {
46 | foreach ($this->headers as $name) {
47 | $this->iAddHeaderEqualTo($name, null);
48 | }
49 | }
50 |
51 | /**
52 | * @Given I set basic authentication with :username and :password
53 | */
54 | public function iAmAuthenticatedAs($username, $password)
55 | {
56 | // Does not work !
57 | // But let it to be future proof
58 | $this->getSession()->setBasicAuth($username, $password);
59 |
60 | // Workaround
61 | $this->iAddHeaderEqualTo('PHP_AUTH_USER', $username);
62 | $this->iAddHeaderEqualTo('PHP_AUTH_PW', $password);
63 | }
64 |
65 | /**
66 | * @Then I set header :name to :value
67 | */
68 | public function iAddHeaderEqualTo($name, $value)
69 | {
70 | $this->headers[] = $name;
71 |
72 | parent::iAddHeaderEqualTo($name, $value);
73 | }
74 |
75 | /**
76 | * @Then the response should be equal to file :filename
77 | */
78 | public function theResponseShouldBeEqualToFile($filename)
79 | {
80 | $fixturesPath = __DIR__.'/../fixtures/';
81 |
82 | $actual = $this->request->getContent();
83 | $message = "The content of file '$filename' is not equal to the response of the current page";
84 | $this->assertEquals(file_get_contents($fixturesPath.$filename), $actual, $message);
85 | }
86 |
87 | /**
88 | * @Then I store value of header :header to variable :name
89 | */
90 | public function iStoreValueOfHeaderToVariable($header, $name)
91 | {
92 | $this->variables[$name] = $this->request->getHttpHeader($header);
93 | }
94 |
95 | /**
96 | * @Override I send a :method request to :url
97 | */
98 | public function iSendARequestTo($method, $url, PyStringNode $body = null)
99 | {
100 | $vars = array_map(function ($val) {return '{'.$val.'}';}, array_keys($this->variables));
101 | $url = strtr($url, array_combine($vars, array_values($this->variables)));
102 |
103 | return $this->request->send(
104 | $method,
105 | $this->locatePath($url),
106 | [],
107 | [],
108 | $body !== null ? $body->getRaw() : null
109 | );
110 | }
111 |
112 | /**
113 | * @Then I send a :method request to :url with body :filename
114 | */
115 | public function iSendARequestToWithBodyFilename($method, $url, $filename)
116 | {
117 | $fixturesPath = __DIR__.'/../fixtures/';
118 |
119 | $vars = array_map(function ($val) {return '{'.$val.'}';}, array_keys($this->variables));
120 | $url = strtr($url, array_combine($vars, array_values($this->variables)));
121 |
122 | return $this->request->send(
123 | $method,
124 | $this->locatePath($url),
125 | [],
126 | [],
127 | file_get_contents($fixturesPath.$filename)
128 | );
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/features/fixtures/layers/03f4658f8b782e12230c1783426bd3bacce651ce582a4ffb6fbbfa2079428ecb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamluc/dunkerque/36662ef4d28dd61bb5cb03748861d46c5329438e/features/fixtures/layers/03f4658f8b782e12230c1783426bd3bacce651ce582a4ffb6fbbfa2079428ecb
--------------------------------------------------------------------------------
/features/fixtures/layers/a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamluc/dunkerque/36662ef4d28dd61bb5cb03748861d46c5329438e/features/fixtures/layers/a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
--------------------------------------------------------------------------------
/features/fixtures/manifests/hello-world:latest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hello-world",
3 | "tag": "latest",
4 | "architecture": "amd64",
5 | "fsLayers": [
6 | {
7 | "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
8 | },
9 | {
10 | "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
11 | },
12 | {
13 | "blobSum": "sha256:03f4658f8b782e12230c1783426bd3bacce651ce582a4ffb6fbbfa2079428ecb"
14 | }
15 | ],
16 | "history": [
17 | {
18 | "v1Compatibility": "{\"id\":\"af340544ed62de0680f441c71fa1a80cb084678fed42bae393e543faea3a572c\",\"parent\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.608577814Z\",\"container\":\"c2b715156f640c7ac7d98472ea24335aba5432a1323a3bb722697e6d37ef794f\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [\\\"/hello\\\"]\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/hello\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"
19 | },
20 | {
21 | "v1Compatibility": "{\"id\":\"af340544ed62de0680f441c71fa1a80cb084678fed42bae393e543faea3a572c\",\"parent\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.608577814Z\",\"container\":\"c2b715156f640c7ac7d98472ea24335aba5432a1323a3bb722697e6d37ef794f\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [\\\"/hello\\\"]\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/hello\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"
22 | },
23 | {
24 | "v1Compatibility": "{\"id\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.241352727Z\",\"container\":\"9aeb0006ffa72a8287564caaea87625896853701459261d3b569e320c0c9d5dc\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) COPY file:4abd3bff60458ca3b079d7b131ce26b2719055a030dfa96ff827da2b7c7038a7 in /\"],\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":960}\n"
25 | }
26 | ],
27 | "schemaVersion": 1,
28 | "signatures": [
29 | {
30 | "header": {
31 | "jwk": {
32 | "crv": "P-256",
33 | "kid": "MPKU:VKH5:HUOL:LSVI:HZ3C:VWZE:MGMT:JWUY:JGOC:4ZNL:PEPS:3WH5",
34 | "kty": "EC",
35 | "x": "nGNhgUR4P65-qIrXj6pIa7dc30ntpJuYS4KIphNf8ks",
36 | "y": "wxFL_4C2wKl603N2W-2GUrvAcarhR0wxwKSXpus21kY"
37 | },
38 | "alg": "ES256"
39 | },
40 | "signature": "FbbW1VrVsunBtacEuC2kTku0ivBKE5kkADVT9F7Tw11dEuQUXBhDkKl8XcLArtODP46QbdyPY8NlbDvhGXdN3A",
41 | "protected": "eyJmb3JtYXRMZW5ndGgiOjQ3ODksImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAxNS0wOS0yNFQxMjo1MzoxM1oifQ"
42 | }
43 | ]
44 | }
--------------------------------------------------------------------------------
/features/fixtures/manifests/secret-world:latest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "secret-world",
3 | "tag": "latest",
4 | "architecture": "amd64",
5 | "fsLayers": [
6 | {
7 | "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
8 | },
9 | {
10 | "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
11 | },
12 | {
13 | "blobSum": "sha256:03f4658f8b782e12230c1783426bd3bacce651ce582a4ffb6fbbfa2079428ecb"
14 | }
15 | ],
16 | "history": [
17 | {
18 | "v1Compatibility": "{\"id\":\"af340544ed62de0680f441c71fa1a80cb084678fed42bae393e543faea3a572c\",\"parent\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.608577814Z\",\"container\":\"c2b715156f640c7ac7d98472ea24335aba5432a1323a3bb722697e6d37ef794f\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [\\\"/hello\\\"]\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/hello\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"
19 | },
20 | {
21 | "v1Compatibility": "{\"id\":\"af340544ed62de0680f441c71fa1a80cb084678fed42bae393e543faea3a572c\",\"parent\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.608577814Z\",\"container\":\"c2b715156f640c7ac7d98472ea24335aba5432a1323a3bb722697e6d37ef794f\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [\\\"/hello\\\"]\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/hello\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"
22 | },
23 | {
24 | "v1Compatibility": "{\"id\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.241352727Z\",\"container\":\"9aeb0006ffa72a8287564caaea87625896853701459261d3b569e320c0c9d5dc\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) COPY file:4abd3bff60458ca3b079d7b131ce26b2719055a030dfa96ff827da2b7c7038a7 in /\"],\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":960}\n"
25 | }
26 | ],
27 | "schemaVersion": 1,
28 | "signatures": [
29 | {
30 | "header": {
31 | "jwk": {
32 | "crv": "P-256",
33 | "kid": "MPKU:VKH5:HUOL:LSVI:HZ3C:VWZE:MGMT:JWUY:JGOC:4ZNL:PEPS:3WH5",
34 | "kty": "EC",
35 | "x": "nGNhgUR4P65-qIrXj6pIa7dc30ntpJuYS4KIphNf8ks",
36 | "y": "wxFL_4C2wKl603N2W-2GUrvAcarhR0wxwKSXpus21kY"
37 | },
38 | "alg": "ES256"
39 | },
40 | "signature": "FbbW1VrVsunBtacEuC2kTku0ivBKE5kkADVT9F7Tw11dEuQUXBhDkKl8XcLArtODP46QbdyPY8NlbDvhGXdN3A",
41 | "protected": "eyJmb3JtYXRMZW5ndGgiOjQ3ODksImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAxNS0wOS0yNFQxMjo1MzoxM1oifQ"
42 | }
43 | ]
44 | }
--------------------------------------------------------------------------------
/features/fixtures/manifests/test~hello-world:latest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test/hello-world",
3 | "tag": "latest",
4 | "architecture": "amd64",
5 | "fsLayers": [
6 | {
7 | "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
8 | },
9 | {
10 | "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
11 | },
12 | {
13 | "blobSum": "sha256:03f4658f8b782e12230c1783426bd3bacce651ce582a4ffb6fbbfa2079428ecb"
14 | }
15 | ],
16 | "history": [
17 | {
18 | "v1Compatibility": "{\"id\":\"af340544ed62de0680f441c71fa1a80cb084678fed42bae393e543faea3a572c\",\"parent\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.608577814Z\",\"container\":\"c2b715156f640c7ac7d98472ea24335aba5432a1323a3bb722697e6d37ef794f\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [\\\"/hello\\\"]\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/hello\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"
19 | },
20 | {
21 | "v1Compatibility": "{\"id\":\"af340544ed62de0680f441c71fa1a80cb084678fed42bae393e543faea3a572c\",\"parent\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.608577814Z\",\"container\":\"c2b715156f640c7ac7d98472ea24335aba5432a1323a3bb722697e6d37ef794f\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [\\\"/hello\\\"]\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/hello\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"
22 | },
23 | {
24 | "v1Compatibility": "{\"id\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.241352727Z\",\"container\":\"9aeb0006ffa72a8287564caaea87625896853701459261d3b569e320c0c9d5dc\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) COPY file:4abd3bff60458ca3b079d7b131ce26b2719055a030dfa96ff827da2b7c7038a7 in /\"],\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":960}\n"
25 | }
26 | ],
27 | "schemaVersion": 1,
28 | "signatures": [
29 | {
30 | "header": {
31 | "jwk": {
32 | "crv": "P-256",
33 | "kid": "MPKU:VKH5:HUOL:LSVI:HZ3C:VWZE:MGMT:JWUY:JGOC:4ZNL:PEPS:3WH5",
34 | "kty": "EC",
35 | "x": "nGNhgUR4P65-qIrXj6pIa7dc30ntpJuYS4KIphNf8ks",
36 | "y": "wxFL_4C2wKl603N2W-2GUrvAcarhR0wxwKSXpus21kY"
37 | },
38 | "alg": "ES256"
39 | },
40 | "signature": "mC26itQhWvCFYjBDz4DctL9Pf1YfsoXDgln08tnB2im_hczzbJOSOqQXDnVDY6JyOl_in00zKJCELYOEBTJtEQ",
41 | "protected": "eyJmb3JtYXRMZW5ndGgiOjQ3OTQsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAxNS0wOS0yNFQxMjo1MjowNloifQ"
42 | }
43 | ]
44 | }
--------------------------------------------------------------------------------
/features/fixtures/manifests/test~secret-world:latest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test/secret-world",
3 | "tag": "latest",
4 | "architecture": "amd64",
5 | "fsLayers": [
6 | {
7 | "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
8 | },
9 | {
10 | "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
11 | },
12 | {
13 | "blobSum": "sha256:03f4658f8b782e12230c1783426bd3bacce651ce582a4ffb6fbbfa2079428ecb"
14 | }
15 | ],
16 | "history": [
17 | {
18 | "v1Compatibility": "{\"id\":\"af340544ed62de0680f441c71fa1a80cb084678fed42bae393e543faea3a572c\",\"parent\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.608577814Z\",\"container\":\"c2b715156f640c7ac7d98472ea24335aba5432a1323a3bb722697e6d37ef794f\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [\\\"/hello\\\"]\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/hello\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"
19 | },
20 | {
21 | "v1Compatibility": "{\"id\":\"af340544ed62de0680f441c71fa1a80cb084678fed42bae393e543faea3a572c\",\"parent\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.608577814Z\",\"container\":\"c2b715156f640c7ac7d98472ea24335aba5432a1323a3bb722697e6d37ef794f\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [\\\"/hello\\\"]\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/hello\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"
22 | },
23 | {
24 | "v1Compatibility": "{\"id\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.241352727Z\",\"container\":\"9aeb0006ffa72a8287564caaea87625896853701459261d3b569e320c0c9d5dc\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) COPY file:4abd3bff60458ca3b079d7b131ce26b2719055a030dfa96ff827da2b7c7038a7 in /\"],\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":960}\n"
25 | }
26 | ],
27 | "schemaVersion": 1,
28 | "signatures": [
29 | {
30 | "header": {
31 | "jwk": {
32 | "crv": "P-256",
33 | "kid": "MPKU:VKH5:HUOL:LSVI:HZ3C:VWZE:MGMT:JWUY:JGOC:4ZNL:PEPS:3WH5",
34 | "kty": "EC",
35 | "x": "nGNhgUR4P65-qIrXj6pIa7dc30ntpJuYS4KIphNf8ks",
36 | "y": "wxFL_4C2wKl603N2W-2GUrvAcarhR0wxwKSXpus21kY"
37 | },
38 | "alg": "ES256"
39 | },
40 | "signature": "mC26itQhWvCFYjBDz4DctL9Pf1YfsoXDgln08tnB2im_hczzbJOSOqQXDnVDY6JyOl_in00zKJCELYOEBTJtEQ",
41 | "protected": "eyJmb3JtYXRMZW5ndGgiOjQ3OTQsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAxNS0wOS0yNFQxMjo1MjowNloifQ"
42 | }
43 | ]
44 | }
--------------------------------------------------------------------------------
/features/homepage.feature:
--------------------------------------------------------------------------------
1 | Feature: Test homepage
2 |
3 | Scenario: Test homepage is correct
4 | Given I am on the homepage
5 | Then the response status code should be 200
6 | Then I should see "Dunkerque"
7 |
8 | @reset-schema
9 | Scenario: Fake background (for performance)
10 | Given I have users:
11 | | username | password | roles |
12 | | admin | admin | ROLE_ADMIN |
13 | | test | test | ROLE_USER |
14 | And the following repositories:
15 | | name | owner | private | pulls |
16 | | hello-world | admin | false | 9876 |
17 | | secret-world | admin | true | 0 |
18 | | test/hello-world | test | false | 42 |
19 | | test/secret-world | test | true | 654 |
20 | When I am on the homepage
21 | Then I should see "hello-world"
22 | And I should see "Most pulled"
23 | And I should see "9876"
24 | And I should not see "secret-world"
25 |
--------------------------------------------------------------------------------
/features/registration.feature:
--------------------------------------------------------------------------------
1 | Feature: Test registration
2 |
3 | Scenario: I can access the registration page
4 | Given I go to "/register/"
5 | Then the response status code should be 200
6 |
--------------------------------------------------------------------------------
/features/registry/access_manifest.feature:
--------------------------------------------------------------------------------
1 | Feature: Test access manifests
2 |
3 | @reset-schema
4 | Scenario: Fake background (for performance)
5 | Given I have users:
6 | | username | password | roles |
7 | | admin | admin | ROLE_ADMIN |
8 | | test | test | ROLE_USER |
9 | And the following repositories:
10 | | name | owner | private |
11 | | hello-world | admin | false |
12 | | secret-world | admin | true |
13 | | test/hello-world | test | false |
14 | | test/secret-world | test | true |
15 | And I have manifests:
16 | | hello-world:latest.json |
17 | | secret-world:latest.json |
18 | | test~hello-world:latest.json |
19 | | test~secret-world:latest.json |
20 |
21 | Scenario: As an anonymous user, I cannot access a private manifest
22 | Given I send a "GET" request to "/v2/test/secret-world/manifests/latest"
23 | Then the response status code should be 401
24 | And the header "Docker-Distribution-Api-Version" should contain "registry/2.0"
25 |
26 | Scenario: As an anonymous user, I can access a public manifest
27 | Given I send a "GET" request to "/v2/test/hello-world/manifests/latest"
28 | Then the response status code should be 200
29 | And the header "Docker-Distribution-Api-Version" should contain "registry/2.0"
30 |
31 | @registry2
32 | Scenario: As a simple user, access unknown manifest from my namespace
33 | Given I set basic authentication with "test" and "test"
34 | When I send a "GET" request to "/v2/test/goodbye-world/manifests/latest"
35 | Then the response status code should be 404
36 | And the header "Docker-Distribution-Api-Version" should contain "registry/2.0"
37 |
38 | Scenario: As a simple user, access public manifest from my namespace
39 | Given I set basic authentication with "test" and "test"
40 | When I send a "GET" request to "/v2/test/hello-world/manifests/latest"
41 | Then the response status code should be 200
42 | And the response should be equal to file "manifests/test~hello-world:latest.json"
43 | And the header "Docker-Distribution-Api-Version" should contain "registry/2.0"
44 | And the header "Content-Type" should contain "application/json"
45 |
46 | Scenario: As a simple user, access private manifest from my namespace
47 | Given I set basic authentication with "test" and "test"
48 | When I send a "GET" request to "/v2/test/secret-world/manifests/latest"
49 | Then the response status code should be 200
50 | And the response should be equal to file "manifests/test~secret-world:latest.json"
51 | And the header "Docker-Distribution-Api-Version" should contain "registry/2.0"
52 | And the header "Content-Type" should contain "application/json"
53 |
54 | Scenario: As a simple user, access private manifest from the root namespace
55 | Given I set basic authentication with "test" and "test"
56 | When I send a "GET" request to "/v2/secret-world/manifests/latest"
57 | Then the response status code should be 403
58 | And the header "Docker-Distribution-Api-Version" should contain "registry/2.0"
59 |
60 | Scenario: As a simple user, access public manifest from the root namespace
61 | Given I set basic authentication with "test" and "test"
62 | When I send a "GET" request to "/v2/hello-world/manifests/latest"
63 | Then the response status code should be 200
64 | And the response should be equal to file "manifests/hello-world:latest.json"
65 | And the header "Docker-Distribution-Api-Version" should contain "registry/2.0"
66 |
67 | Scenario: As an admin, access private manifest from the root namespace
68 | Given I set basic authentication with "admin" and "admin"
69 | When I send a "GET" request to "/v2/hello-world/manifests/latest"
70 | Then the response status code should be 200
71 | And the response should be equal to file "manifests/hello-world:latest.json"
72 | And the header "Docker-Distribution-Api-Version" should contain "registry/2.0"
73 |
--------------------------------------------------------------------------------
/features/registry/token.feature:
--------------------------------------------------------------------------------
1 | Feature: Test token endpoint
2 |
3 | Scenario: As an anonymous user, I can access token endpoint
4 | Given I send a "GET" request to "/token"
5 | Then the response status code should be 200
6 | And the JSON node "token" should exist
7 | And the header "Docker-Distribution-Api-Version" should contain "registry/2.0"
8 |
--------------------------------------------------------------------------------
/features/registry/upload_manifest.feature:
--------------------------------------------------------------------------------
1 | Feature: Test add manifest
2 |
3 | @reset-schema
4 | Scenario: Fake background (for performance)
5 | Given I have users:
6 | | username | password | roles |
7 | | test | test | ROLE_USER |
8 |
9 | Scenario: As an anonymous user, I cannot upload a manifest
10 | Given I send a "POST" request to "/v2/test/hello-world/blobs/uploads/"
11 | Then the response status code should be 401
12 |
13 | @registry2
14 | Scenario: As a simple user, upload layers and add a manifest
15 | Given I set basic authentication with "test" and "test"
16 |
17 | When I send a "POST" request to "/v2/test/hello-world/blobs/uploads/"
18 | Then the response status code should be 202
19 | And the header "Docker-Upload-UUID" should contain "-"
20 | And I store value of header "Location" to variable "location"
21 |
22 | When I send a "PUT" request to "{location}&digest=sha256%3Aa3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4" with body "layers/a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
23 | Then the response status code should be 201
24 | And the header "Location" should contain "/v2/test/hello-world/blobs/sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
25 | And the header "Docker-Content-Digest" should be equal to "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
26 |
27 | When I send a "HEAD" request to "/v2/test/hello-world/blobs/sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
28 | Then the response status code should be 200
29 |
30 | When I send a "POST" request to "/v2/test/hello-world/blobs/uploads/"
31 | Then the response status code should be 202
32 | And the header "Docker-Upload-UUID" should contain "-"
33 | And I store value of header "Location" to variable "location"
34 |
35 | When I send a "PUT" request to "{location}&digest=sha256%3A03f4658f8b782e12230c1783426bd3bacce651ce582a4ffb6fbbfa2079428ecb" with body "layers/03f4658f8b782e12230c1783426bd3bacce651ce582a4ffb6fbbfa2079428ecb"
36 | Then the response status code should be 201
37 | And the header "Location" should contain "/v2/test/hello-world/blobs/sha256:03f4658f8b782e12230c1783426bd3bacce651ce582a4ffb6fbbfa2079428ecb"
38 | And the header "Docker-Content-Digest" should be equal to "sha256:03f4658f8b782e12230c1783426bd3bacce651ce582a4ffb6fbbfa2079428ecb"
39 |
40 | When I send a "PUT" request to "/v2/test/hello-world/manifests/latest" with body "manifests/test~hello-world:latest.json"
41 | Then the response status code should be 201
42 | And the header "Location" should contain "/v2/test/hello-world/manifests/sha256:9956e7d769a4cfeba2e342b92adf58e403affd8a77ef0710c4fb01e948fc2bbe"
43 | And the header "Docker-Content-Digest" should be equal to "sha256:9956e7d769a4cfeba2e342b92adf58e403affd8a77ef0710c4fb01e948fc2bbe"
44 |
45 | @registry2
46 | Scenario: As a simple user, upload layer with PATCH
47 | Given I set basic authentication with "test" and "test"
48 |
49 | When I send a "POST" request to "/v2/test/hello-world/blobs/uploads/"
50 | Then the response status code should be 202
51 | And the header "Docker-Upload-UUID" should contain "-"
52 | And I store value of header "Location" to variable "location"
53 |
54 | When I send a "PATCH" request to "{location}" with body "layers/03f4658f8b782e12230c1783426bd3bacce651ce582a4ffb6fbbfa2079428ecb"
55 | Then the response status code should be 202
56 | And the header "Range" should contain "0-600"
57 |
58 | When I send a "PUT" request to "{location}&digest=sha256%3A03f4658f8b782e12230c1783426bd3bacce651ce582a4ffb6fbbfa2079428ecb"
59 | Then the response status code should be 201
60 | And the header "Location" should contain "/v2/test/hello-world/blobs/sha256:03f4658f8b782e12230c1783426bd3bacce651ce582a4ffb6fbbfa2079428ecb"
61 | And the header "Docker-Content-Digest" should be equal to "sha256:03f4658f8b782e12230c1783426bd3bacce651ce582a4ffb6fbbfa2079428ecb"
62 |
--------------------------------------------------------------------------------
/features/registry/version.feature:
--------------------------------------------------------------------------------
1 | Feature: Test version endpoint
2 |
3 | Scenario: As an anonymous user, I cannot access version endpoint
4 | Given I send a "GET" request to "/v2/"
5 | Then the response status code should be 401
6 | And the response should be in JSON
7 | And the JSON node "errors[0].code" should be equal to "UNAUTHORIZED"
8 | And the JSON node "errors[0].message" should be equal to "access to the requested resource is not authorized"
9 | And the header "WWW-Authenticate" should contain 'Bearer realm="http'
10 | And the header "Docker-Distribution-Api-Version" should contain "registry/2.0"
11 |
12 | @reset-schema
13 | Scenario: As a user with valid credentials, I can access version endpoint
14 | Given I have users:
15 | | username | password |
16 | | test | test |
17 | When I set basic authentication with "test" and "test"
18 | And I send a "GET" request to "/v2/"
19 | Then the response status code should be 200
20 | And the response should be in JSON
21 | And the JSON should be equal to:
22 | """
23 | {}
24 | """
25 | And the header "Docker-Distribution-Api-Version" should contain "registry/2.0"
26 |
27 | Scenario: As a user with invalid credentials, I cannot access version endpoint
28 | Given I set basic authentication with "test_KO" and "test1234"
29 | And I send a "GET" request to "/v2/"
30 | Then the response status code should be 401
31 | And the response should be in JSON
32 | And the JSON node "errors[0].code" should be equal to "UNAUTHORIZED"
33 | And the JSON node "errors[0].message" should be equal to "access to the requested resource is not authorized"
34 | And the header "WWW-Authenticate" should contain "Bearer"
35 | And the header "Docker-Distribution-Api-Version" should contain "registry/2.0"
36 |
--------------------------------------------------------------------------------
/features/repository_description.feature:
--------------------------------------------------------------------------------
1 | Feature: Test repository description page
2 |
3 | @reset-schema
4 | Scenario: Fake background (for performance)
5 | Given I have users:
6 | | username | password | roles |
7 | | admin | admin | ROLE_ADMIN |
8 | | test | test | ROLE_USER |
9 | And the following repositories:
10 | | name | owner | private | stars | pulls | description |
11 | | hello-world | admin | true | 3 | 12 | |
12 | | dummy | admin | false | 0 | 0 | Dunkerque dummy image |
13 | | dunkerque | admin | false | 1000 | 234 | Dunkerque official image |
14 | And the following repositoryStars:
15 | | user | repository |
16 | | test | dunkerque |
17 | | admin | dunkerque |
18 | And I have manifests:
19 | | hello-world:latest.json |
20 |
21 | Scenario: I go to a non-existing repository
22 | Given I go to "/r/admin/docker"
23 | Then the response status code should be 404
24 |
25 | Scenario: As an anonymous user, I go to a private repository
26 | Given I go to "/r/hello-world"
27 | Then the response status code should be 200
28 | And I should be on "/login"
29 |
30 | Scenario: As an anonymous user, I go to a public repository
31 | Given I go to "/r/dunkerque"
32 | Then the response status code should be 200
33 | And I should see "Dunkerque official image"
34 | And I should not see an "a#repository-star" element
35 |
36 | Scenario: As an authenticated user, I go to a public repository
37 | Given I am authenticated as "test"
38 | And I go to "/r/dunkerque"
39 | Then I should see an "a#repository-star" element
40 | And I should see an "span.glyphicon.glyphicon-star" element
41 | Given I go to "/r/dummy"
42 | Then I should see an "a#repository-star" element
43 | And I should see an "span.glyphicon.glyphicon-star-empty" element
44 |
45 | Scenario: As an authenticated user, I go to a private repository
46 | Given I am authenticated as "test"
47 | And I go to "/r/hello-world"
48 | Then the response status code should be 403
49 |
--------------------------------------------------------------------------------
/features/search.feature:
--------------------------------------------------------------------------------
1 | Feature: Test search
2 |
3 | @reset-schema
4 | Scenario: Fake background (for performance)
5 | Given I have users:
6 | | username | password | roles |
7 | | admin | admin | ROLE_ADMIN |
8 | | test | test | ROLE_USER |
9 | And the following repositories:
10 | | name | owner | private | stars | pulls | description |
11 | | not_found | admin | false | 3 | 12 | Not found |
12 | | admin_public_1 | admin | false | 3 | 12 | Dunkerque official image |
13 | | admin_public_2 | admin | false | 0 | 0 | Dunkerque almost official image |
14 | | admin_private_1 | admin | true | 1000 | 234 | Dunkerque |
15 | | test_private_1 | test | true | 1000 | 234 | Dunkerque |
16 | And Entities are indexed
17 |
18 | Scenario: I search a non-existing repository
19 | Given I go to "/"
20 | And I fill in "search_keyword" with "unknown"
21 | And I press "search_submit"
22 | Then the response status code should be 200
23 | And I should see "No result found for \"unknown\""
24 | And I should not see an "table" element
25 |
26 | Scenario: I search public repositories
27 | Given I go to "/"
28 | And I fill in "search_keyword" with "Dunkerque"
29 | And I press "search_submit"
30 | Then the response status code should be 200
31 | And I should see "2 results found for \"Dunkerque\""
32 | And I should see the following table:
33 | | Name | Stars | Pulls | Private |
34 | | admin_public_1 | 3 | 12 | Public |
35 | | admin_public_2 | 0 | 0 | Public |
36 |
37 | Scenario: As a user, I search public and private repositories
38 | Given I am authenticated as "test"
39 | And I go to "/"
40 | And I fill in "search_keyword" with "Dunkerque"
41 | And I press "search_submit"
42 | Then the response status code should be 200
43 | And I should see "3 results found for \"Dunkerque\""
44 | And I should see the following table:
45 | | Name | Stars | Pulls | Private |
46 | | test_private_1 | 1000 | 234 | Private |
47 | | admin_public_1 | 3 | 12 | Public |
48 | | admin_public_2 | 0 | 0 | Public |
49 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp'),
2 | concat = require('gulp-concat'),
3 | sass = require('gulp-ruby-sass'),
4 | uglify = require('gulp-uglify');
5 |
6 | /**
7 | * Config
8 | */
9 | var config = {
10 | bowerDir: './bower_components',
11 | assetsSrc: './app/Resources/assets',
12 | assetsDest: './web/assets'
13 | };
14 |
15 | config.fonts = {
16 | src: config.bowerDir + '/bootstrap-sass-official/assets/fonts/bootstrap/*',
17 | dest: config.assetsDest + '/fonts/bootstrap'
18 | };
19 |
20 | config.sass = {
21 | src: config.assetsSrc + '/scss/main.scss',
22 | dest: config.assetsDest + '/css',
23 | loadPath: [
24 | config.bowerDir + '/bootstrap-sass-official/assets/stylesheets'
25 | ]
26 | };
27 |
28 | config.js = {
29 | src: [
30 | config.bowerDir + '/jquery/dist/jquery.js',
31 | config.bowerDir + '/bootstrap-sass-official/assets/javascripts/bootstrap.js',
32 | config.assetsSrc + '/js/*.js'
33 | ],
34 | dest: config.assetsDest + '/js'
35 | };
36 |
37 | /**
38 | * Tasks
39 | */
40 | gulp.task('fonts', function() {
41 | return gulp
42 | .src(config.fonts.src)
43 | .pipe(gulp.dest(config.fonts.dest));
44 | });
45 |
46 | gulp.task('css', function() {
47 | return sass(config.sass.src, {
48 | style: 'compressed',
49 | loadPath: config.sass.loadPath
50 | })
51 | .on('error', sass.logError)
52 | .pipe(gulp.dest(config.sass.dest));
53 | });
54 |
55 | gulp.task('js', function () {
56 | return gulp
57 | .src(config.js.src)
58 | .pipe(concat('main.js'))
59 | .pipe(uglify())
60 | .pipe(gulp.dest(config.js.dest));
61 | });
62 |
63 | // Rerun the task when a file changes
64 | gulp.task('watch', function() {
65 | gulp.watch(config.assetsSrc + '/scss/*.scss', ['css']);
66 | gulp.watch(config.assetsSrc + '/js/*.js', ['js']);
67 | });
68 |
69 | gulp.task('default', ['fonts', 'css', 'js']);
70 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "license": "MIT",
3 | "repository": {
4 | "type" : "git",
5 | "url" : "https://github.com/iamluc/dunkerque.git"
6 | },
7 | "devDependencies": {
8 | "bower": "^1.7.2",
9 | "gulp": "^3.9.0",
10 | "gulp-concat": "^2.6.0",
11 | "gulp-ruby-sass": "^2.0.3",
12 | "gulp-uglify": "^1.4.1"
13 | },
14 | "scripts": {
15 | "install": "bower install"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | tests
18 |
19 |
20 |
21 |
22 |
23 | src
24 |
25 | src/*Bundle/Resources
26 | src/*/*Bundle/Resources
27 | src/*/Bundle/*Bundle/Resources
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/.htaccess:
--------------------------------------------------------------------------------
1 |
2 | Require all denied
3 |
4 |
5 | Order deny,allow
6 | Deny from all
7 |
8 |
--------------------------------------------------------------------------------
/src/AppBundle/AppBundle.php:
--------------------------------------------------------------------------------
1 | manager = $manager;
21 | }
22 |
23 | public function process(Message $message, array $options)
24 | {
25 | $data = json_decode($message->getBody(), true);
26 |
27 | $manifest = $this->manager->getRepository('AppBundle:Manifest')->find($data['id']);
28 | $webhooks = $this->manager->getRepository('AppBundle:Webhook')->findByRepository($manifest->getRepository());
29 |
30 | $payload = $this->getPayload($manifest);
31 |
32 | /** @var Webhook $webhook */
33 | foreach ($webhooks as $webhook) {
34 | $options = [
35 | 'http' => [
36 | 'method' => 'POST',
37 | 'header' => "Content-Type: application/json\r\n",
38 | 'content' => $payload,
39 | 'timeout' => 2,
40 | 'ignore_errors' => true,
41 | ],
42 | ];
43 | $context = stream_context_create($options);
44 | $response = @file_get_contents($webhook->getUrl(), null, $context);
45 | if (isset($http_response_header) && is_array($http_response_header)) {
46 | $status = $http_response_header[0];
47 | } else {
48 | $status = 'Error';
49 | }
50 | unset($http_response_header);
51 |
52 | $webhook->setLastStatus($status);
53 | $webhook->setLastCall(new \DateTime());
54 | }
55 |
56 | $this->manager->flush();
57 | }
58 |
59 | private function getPayload(Manifest $manifest)
60 | {
61 | $repository = $manifest->getRepository();
62 |
63 | $data = [
64 | 'push_data' => [
65 | 'pushed_at' => time(),
66 | ],
67 | 'repository' => [
68 | 'status' => 'Active',
69 | 'description' => $repository->getTitle(),
70 | 'full_description' => $repository->getDescription(),
71 | 'repo_name' => $repository->getName(),
72 | 'is_private' => $repository->isPrivate(),
73 | 'star_count' => $repository->getStars(),
74 | ],
75 | ];
76 |
77 | return json_encode($data);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/AppBundle/Command/SetupBrokerCommand.php:
--------------------------------------------------------------------------------
1 | setName('dunkerque:broker:setup')
23 | ->setDescription('Setup broker')
24 | ;
25 | }
26 |
27 | protected function execute(InputInterface $input, OutputInterface $output)
28 | {
29 | $input = new ArrayInput(
30 | [
31 | 'filepath' => 'app/Resources/broker/mapping.yml',
32 | '--vhost' => '/',
33 | ],
34 | new InputDefinition([
35 | new InputArgument('filepath'),
36 | new InputOption('vhost'),
37 | new InputOption('erase-vhost'),
38 | ])
39 | );
40 |
41 | parent::execute($input, $output);
42 | }
43 |
44 | protected function getCredentials(InputInterface $input, OutputInterface $output)
45 | {
46 | return [
47 | 'host' => $this->container->getParameter('rabbitmq_host'),
48 | 'port' => $this->container->getParameter('rabbitmq_management_port'),
49 | 'user' => $this->container->getParameter('rabbitmq_login'),
50 | 'password' => $this->container->getParameter('rabbitmq_password'),
51 | ];
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/AppBundle/Controller/AccountController.php:
--------------------------------------------------------------------------------
1 | getUser() && ($account === $this->getUser());
25 |
26 | $qb = $this->get('doctrine')->getRepository('AppBundle:Repository')->findByAccount($account, $isOwner);
27 | $pager = $this->get('datatheke.pager')->createHttpPager($qb);
28 |
29 | /** @var PagerView $view */
30 | $view = $pager->handleRequest($request);
31 | $view->setParameters(['account' => $account->getUsername()]);
32 |
33 | return [
34 | 'isOwner' => $isOwner,
35 | 'account' => $account,
36 | 'pager' => $view,
37 | ];
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/AppBundle/Controller/Admin/RepositoryController.php:
--------------------------------------------------------------------------------
1 | get('datatheke.datagrid')->createHttpDataGrid('AppBundle:Repository');
23 |
24 | return [
25 | 'datagrid' => $datagrid->handleRequest($request),
26 | ];
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/AppBundle/Controller/Admin/UserController.php:
--------------------------------------------------------------------------------
1 | get('datatheke.datagrid')->createHttpDataGrid('AppBundle:User');
23 | $datagrid->showOnly(['username', 'email', 'enabled', 'id', 'lastLogin', 'locked']);
24 |
25 | return [
26 | 'datagrid' => $datagrid->handleRequest($request),
27 | ];
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/AppBundle/Controller/DefaultController.php:
--------------------------------------------------------------------------------
1 | get('doctrine')->getRepository('AppBundle:Repository')->getLatestPublic();
27 |
28 | return [
29 | 'repositories' => $repositories,
30 | ];
31 | }
32 |
33 | /**
34 | * @Template("default/most_stared_repositories.html.twig")
35 | */
36 | public function mostStaredRepositoriesAction()
37 | {
38 | $repositories = $this->get('doctrine')->getRepository('AppBundle:Repository')->getMostStared();
39 |
40 | return [
41 | 'repositories' => $repositories,
42 | ];
43 | }
44 |
45 | /**
46 | * @Template("default/most_pulled_repositories.html.twig")
47 | */
48 | public function mostPulledRepositoriesAction()
49 | {
50 | $repositories = $this->get('doctrine')->getRepository('AppBundle:Repository')->getMostPulled();
51 |
52 | return [
53 | 'repositories' => $repositories,
54 | ];
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/AppBundle/Controller/Registry/LayerController.php:
--------------------------------------------------------------------------------
1 | $layer->getDigest(),
38 | 'Content-Length' => $this->get('layer_manager')->getSize($layer),
39 | 'Content-Type' => 'application/octet-stream',
40 | ];
41 |
42 | if ($request->isMethod('HEAD')) {
43 | return new Response('', Response::HTTP_OK, $headers);
44 | }
45 |
46 | return new StreamedResponse(function () use ($layer) {
47 | fpassthru($this->get('layer_manager')->read($layer));
48 | }, Response::HTTP_OK, $headers);
49 | }
50 |
51 | /**
52 | * @Route("/uploads/", methods={"POST"}, name="layer_new")
53 | *
54 | * @Security("is_granted('REPO_WRITE', name)")
55 | *
56 | * @link http://docs.docker.com/registry/spec/api/#starting-an-upload
57 | */
58 | public function newAction($name)
59 | {
60 | // FIXME: keep a link between the layer and the repository ?
61 |
62 | // Create the repository if it does not exist yet
63 | $repository = $this->get('doctrine')->getRepository('AppBundle:Repository')->findByNameOrCreate($name, $this->getUser());
64 |
65 | $layer = $this->get('layer_manager')->create();
66 | $this->get('layer_manager')->save($layer);
67 |
68 | return new Response('', Response::HTTP_ACCEPTED, [
69 | 'Location' => $this->generateUrl('layer_upload', [
70 | 'name' => $name,
71 | 'uuid' => $layer->getUuid(),
72 | '_state' => uniqid(), // FIXME: not implemented
73 | ], UrlGeneratorInterface::ABSOLUTE_URL),
74 | 'Docker-Upload-UUID' => $layer->getUuid(),
75 | ]);
76 | }
77 |
78 | /**
79 | * @Route("/uploads/{uuid}", methods={"GET"}, name="layer_upload_status")
80 | *
81 | * @ParamConverter(name="repository", options={"mapping": {"name": "name"}})
82 | * @Security("is_granted('REPO_WRITE', repository)")
83 | *
84 | * @link http://docs.docker.com/registry/spec/api/#upload-progress
85 | */
86 | public function uploadStatusAction(Repository $repository)
87 | {
88 | return new Response('', 404);
89 | }
90 |
91 | /**
92 | * @Route("/uploads/{uuid}", methods={"PUT", "PATCH"}, name="layer_upload", requirements={"uuid"="[0-9a-z-]+"})
93 | *
94 | * @ParamConverter(name="repository", options={"mapping": {"name": "name"}})
95 | * @ParamConverter(name="layer", options={"mapping": {"uuid": "uuid"}})
96 | *
97 | * @Security("is_granted('REPO_WRITE', repository)")
98 | *
99 | * @link http://docs.docker.com/registry/spec/api/#uploading-the-layer
100 | */
101 | public function uploadAction(Request $request, Repository $repository, Layer $layer)
102 | {
103 | if (Layer::STATUS_COMPLETE === $layer->getStatus()) {
104 | throw new BadRequestHttpException(sprintf('Layer with uuid "%s" has already been uploaded', $layer->getUuid()));
105 | }
106 |
107 | $finalUpload = $request->query->has('digest');
108 | $this->get('layer_manager')->write($layer, $request->getContent(true));
109 |
110 | if (!$finalUpload) {
111 | $layer->setStatus(Layer::STATUS_PARTIAL);
112 | $this->get('layer_manager')->save($layer);
113 |
114 | return new Response('', Response::HTTP_ACCEPTED, [
115 | 'Location' => $this->generateUrl('layer_upload', [
116 | 'name' => $repository->getName(),
117 | 'uuid' => $layer->getUuid(),
118 | ], UrlGeneratorInterface::ABSOLUTE_URL),
119 | 'Docker-Upload-UUID' => $layer->getUuid(),
120 | 'Range' => '0-'.($this->get('layer_manager')->getSize($layer) - 1), // FIXME: need '-1' to be compatible with registry:2
121 | ]);
122 | }
123 |
124 | $digest = $this->get('layer_manager')->computeDigest($layer);
125 |
126 | if ($digest !== $request->query->get('digest')) {
127 | throw new BadRequestHttpException(sprintf('Digest does not match with received data (computed: "%s")', $digest));
128 | }
129 |
130 | $layer->setDigest($digest);
131 | $layer->setStatus(Layer::STATUS_COMPLETE);
132 | $this->get('layer_manager')->save($layer);
133 |
134 | return new Response('', Response::HTTP_CREATED, [
135 | 'Location' => $this->generateUrl('layer_get', [
136 | 'name' => $repository->getName(),
137 | 'digest' => $layer->getDigest(),
138 | ], UrlGeneratorInterface::ABSOLUTE_URL),
139 | 'Docker-Content-Digest' => $layer->getDigest(),
140 | ]);
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/AppBundle/Controller/Registry/ManifestController.php:
--------------------------------------------------------------------------------
1 | get('event_dispatcher')->dispatch('delayed', new DelayedEvent('kernel.terminate', 'manifest.pull', $event));
39 |
40 | return new Response($manifest->getContent(), Response::HTTP_OK, [
41 | 'Content-Type' => 'application/json; charset=utf-8',
42 | ]);
43 | }
44 |
45 | /**
46 | * @Route("/{reference}", methods={"PUT"}, name="manifest_put")
47 | *
48 | * @ParamConverter("manifest", options={"repository_method": "findOneByReferenceOrCreate", "map_method_signature": true})
49 | *
50 | * @Security("is_granted('REPO_WRITE', repository)")
51 | *
52 | * @link http://docs.docker.com/registry/spec/api/#put-manifest
53 | */
54 | public function uploadAction(Request $request, Repository $repository, Manifest $manifest, $reference)
55 | {
56 | $manifest->setContent($request->getContent());
57 |
58 | if ($reference !== $manifest->getTag() && $reference !== $manifest->getDigest()) {
59 | throw new BadRequestHttpException('Provided reference does not match with tag or digest.');
60 | }
61 |
62 | // TODO: validate layers & signatures
63 |
64 | $this->get('doctrine')->getRepository('AppBundle:Manifest')->save($manifest);
65 |
66 | // Dispatch event
67 | $event = new ManifestEvent($manifest);
68 | $this->get('event_dispatcher')->dispatch('delayed', new DelayedEvent('kernel.terminate', 'manifest.push', $event));
69 |
70 | return new Response('', Response::HTTP_CREATED, [
71 | 'Location' => $this->generateUrl('manifest_get', [
72 | 'name' => $manifest->getRepository()->getName(),
73 | 'reference' => $manifest->getDigest(),
74 | ], UrlGeneratorInterface::ABSOLUTE_URL),
75 | 'Docker-Content-Digest' => $manifest->getDigest(),
76 | ]);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/AppBundle/Controller/Registry/TokenController.php:
--------------------------------------------------------------------------------
1 | getUser();
23 | if (null === $user) {
24 | $user = new User('Anon.', null);
25 | }
26 |
27 | $token = $this->get('lexik_jwt_authentication.jwt_manager')->create($user);
28 |
29 | return new JsonResponse([
30 | 'token' => $token,
31 | ]);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/AppBundle/Controller/Registry/VersionController.php:
--------------------------------------------------------------------------------
1 | 'application/json; charset=utf-8',
27 | ]);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/AppBundle/Controller/RepositoryController.php:
--------------------------------------------------------------------------------
1 | isGranted('REPO_WRITE', $repository)) {
33 | $form = $this->createForm(RepositoryType::class, $repository);
34 | $form->handleRequest($request);
35 | if ($form->isValid()) {
36 | $this->get('doctrine')->getManager()->flush();
37 |
38 | return $this->redirect($this->generateUrl('repository', [
39 | 'name' => $repository->getName(),
40 | ]));
41 | }
42 | }
43 |
44 | return [
45 | 'repository' => $repository,
46 | 'form' => isset($form) ? $form->createView() : null,
47 | ];
48 | }
49 |
50 | /**
51 | * @Route("/r/{name}/~/{action}", requirements={"name"="%regex_name%", "action":"^star|unstar$"}, methods={"PUT"}, name="repository_star")
52 | *
53 | * @ParamConverter("repository", options={"mapping": {"name": "name"}})
54 | *
55 | * @Security("is_granted('ROLE_USER', repository)")
56 | */
57 | public function starOrUnstarAction(Repository $repository, $action)
58 | {
59 | $manager = $this->get('repository_star_manager');
60 | $user = $this->getUser();
61 |
62 | $starred = $manager->isStarredByUser($repository, $user);
63 |
64 | if (('star' === $action && !$starred) || ('unstar' === $action && $starred)) {
65 | $manager->$action($repository, $user);
66 | $starred = !$starred;
67 | }
68 |
69 | return new JsonResponse(['starred' => $starred]);
70 | }
71 |
72 | /**
73 | * @Route("/r/{name}/~/webhooks", requirements={"name"="%regex_name%"}, methods={"GET", "POST"}, name="repository_webhooks")
74 | *
75 | * @ParamConverter("repository", options={"mapping": {"name": "name"}})
76 | *
77 | * @Security("is_granted('REPO_WRITE', repository)")
78 | *
79 | * @Template("repository/webhooks.html.twig")
80 | */
81 | public function webhooksAction(Request $request, Repository $repository)
82 | {
83 | $form = $this->createForm(WebhookType::class, new Webhook($repository));
84 | $form->handleRequest($request);
85 | if ($form->isValid()) {
86 | $em = $this->get('doctrine')->getManager();
87 | $em->persist($form->getData());
88 | $em->flush();
89 |
90 | $this->addFlash('success', 'webhook.created');
91 |
92 | return $this->redirect($this->generateUrl('repository_webhooks', [
93 | 'name' => $repository->getName(),
94 | ]));
95 | }
96 |
97 | $pager = $this->get('datatheke.pager')->createHttpPager('AppBundle:Webhook');
98 | $pager->setFilter(new Filter(['repository'], [$repository]), 'base');
99 |
100 | /** @var PagerView $pagerView */
101 | $pagerView = $pager->handleRequest($request);
102 | $pagerView->setParameters(['name' => $repository->getName()]);
103 |
104 | return [
105 | 'repository' => $repository,
106 | 'form' => $form->createView(),
107 | 'pager' => $pagerView,
108 | ];
109 | }
110 |
111 | /**
112 | * @Route("/r/{name}/~/webhooks/{id}/remove", requirements={"name"="%regex_name%"}, methods={"GET", "POST"}, name="repository_webhooks_remove")
113 | *
114 | * @ParamConverter("repository", options={"mapping": {"name": "name"}})
115 | *
116 | * @Security("is_granted('REPO_WRITE', repository)")
117 | */
118 | public function removeWebhookAction(Request $request, Repository $repository, $id)
119 | {
120 | if (!$this->isCsrfTokenValid('webhook_remove', $request->query->get('_token'))) {
121 | $this->addFlash('error', 'invalid_csrf_token');
122 |
123 | return $this->redirectToRoute('repository_webhooks', ['name' => $repository->getName()]);
124 | }
125 |
126 | $webhook = $this->get('doctrine')->getRepository('AppBundle:Webhook')->findOneBy([
127 | 'repository' => $repository,
128 | 'id' => $id,
129 | ]);
130 | if (null === $webhook) {
131 | $this->addFlash('error', 'webhook.not_found_or_not_granted');
132 |
133 | return $this->redirectToRoute('repository_webhooks', ['name' => $repository->getName()]);
134 | }
135 |
136 | $manager = $this->get('doctrine')->getManager();
137 | $manager->remove($webhook);
138 | $manager->flush();
139 |
140 | $this->addFlash('success', 'webhook.removed');
141 |
142 | return $this->redirectToRoute('repository_webhooks', ['name' => $repository->getName()]);
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/AppBundle/Controller/SearchController.php:
--------------------------------------------------------------------------------
1 | query->get('q', '');
22 |
23 | /** @var HttpPagerInterface $pager */
24 | $pager = $this->get('search_manager')->createPager($keyword);
25 |
26 | /** @var PagerView $view */
27 | $view = $pager->handleRequest($request);
28 | $view->setParameters(['q' => $keyword]);
29 |
30 | return [
31 | 'keyword' => $keyword,
32 | 'pager' => $view,
33 | ];
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/AppBundle/Entity/Layer.php:
--------------------------------------------------------------------------------
1 | uuid = $uuid ?: Uuid::uuid4()->toString();
56 | }
57 |
58 | /**
59 | * Get id.
60 | *
61 | * @return int
62 | */
63 | public function getId()
64 | {
65 | return $this->id;
66 | }
67 |
68 | /**
69 | * Get uuid.
70 | *
71 | * @return guid
72 | */
73 | public function getUuid()
74 | {
75 | return $this->uuid;
76 | }
77 |
78 | /**
79 | * Set digest.
80 | *
81 | * @param string $digest
82 | *
83 | * @return Layer
84 | */
85 | public function setDigest($digest)
86 | {
87 | $this->digest = $digest;
88 |
89 | return $this;
90 | }
91 |
92 | /**
93 | * Get digest.
94 | *
95 | * @return string
96 | */
97 | public function getDigest()
98 | {
99 | return $this->digest;
100 | }
101 |
102 | /**
103 | * @return int
104 | */
105 | public function getStatus()
106 | {
107 | return $this->status;
108 | }
109 |
110 | /**
111 | * @param int $status
112 | *
113 | * @return Layer
114 | */
115 | public function setStatus($status)
116 | {
117 | $this->status = $status;
118 |
119 | return $this;
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/AppBundle/Entity/Manifest.php:
--------------------------------------------------------------------------------
1 | repository = $repository;
77 | $this->updatedAt = new \DateTime();
78 | }
79 |
80 | /**
81 | * Get id.
82 | *
83 | * @return int
84 | */
85 | public function getId()
86 | {
87 | return $this->id;
88 | }
89 |
90 | /**
91 | * @return Repository
92 | */
93 | public function getRepository()
94 | {
95 | return $this->repository;
96 | }
97 |
98 | /**
99 | * @param Repository $repository
100 | *
101 | * @return Manifest
102 | */
103 | public function setRepository($repository)
104 | {
105 | $this->repository = $repository;
106 |
107 | return $this;
108 | }
109 |
110 | /**
111 | * Get tag.
112 | *
113 | * @return string
114 | */
115 | public function getTag()
116 | {
117 | return $this->tag;
118 | }
119 |
120 | /**
121 | * Get digest.
122 | *
123 | * @return string
124 | */
125 | public function getDigest()
126 | {
127 | return $this->digest;
128 | }
129 |
130 | /**
131 | * Set content.
132 | *
133 | * @param string $content
134 | *
135 | * @return Manifest
136 | */
137 | public function setContent($content)
138 | {
139 | $this->content = $content;
140 | $this->digest = $this->computeDigest($content);
141 |
142 | $decoded = json_decode($content, true);
143 | $this->tag = $decoded['tag'];
144 |
145 | return $this;
146 | }
147 |
148 | /**
149 | * Get content.
150 | *
151 | * @return string
152 | */
153 | public function getContent()
154 | {
155 | return $this->content;
156 | }
157 |
158 | /**
159 | * Return the digest of the provided manifest.
160 | *
161 | * @param $manifest
162 | *
163 | * @return string
164 | */
165 | protected function computeDigest($manifest)
166 | {
167 | // See func `ParsePrettySignature` in https://github.com/docker/libtrust/blob/master/jsonsign.go
168 | $decoded = json_decode($manifest, true);
169 | $formatLength = null;
170 | $formatTail = null;
171 | foreach ($decoded['signatures'] as $signature) {
172 | $header = json_decode(base64_decode($signature['protected']), true);
173 |
174 | $formatLength = $header['formatLength'];
175 | $formatTail = $header['formatTail'];
176 | }
177 |
178 | // Manifest without signatures
179 | $manifest = substr($manifest, 0, $formatLength).base64_decode($formatTail);
180 |
181 | // Digest
182 | return 'sha256:'.hash('sha256', $manifest);
183 | }
184 |
185 | /**
186 | * @return int
187 | */
188 | public function getPulls()
189 | {
190 | return $this->pulls;
191 | }
192 |
193 | /**
194 | * @param int $pulls
195 | *
196 | * @return $this
197 | */
198 | public function setPulls($pulls)
199 | {
200 | $this->pulls = $pulls;
201 |
202 | return $this;
203 | }
204 |
205 | /**
206 | * @return \DateTime
207 | */
208 | public function getUpdatedAt()
209 | {
210 | return $this->updatedAt;
211 | }
212 |
213 | /**
214 | * @param \DateTime $updatedAt
215 | *
216 | * @return $this
217 | */
218 | public function setUpdatedAt($updatedAt)
219 | {
220 | $this->updatedAt = $updatedAt;
221 |
222 | return $this;
223 | }
224 | }
225 |
--------------------------------------------------------------------------------
/src/AppBundle/Entity/ManifestRepository.php:
--------------------------------------------------------------------------------
1 | createQueryBuilder('m')
12 | ->where('m.repository = :repository')
13 | ->andWhere('(m.digest = :reference OR m.tag = :reference)')
14 | ->setParameters([
15 | 'repository' => $repository,
16 | 'reference' => $reference,
17 | ])
18 | ;
19 |
20 | return $qb->getQuery()->getOneOrNullResult();
21 | }
22 |
23 | public function findOneByReferenceOrCreate(Repository $repository, $reference)
24 | {
25 | $manifest = $this->findOneByReference($repository, $reference);
26 | if (null === $manifest) {
27 | $manifest = $this->create($repository);
28 | }
29 |
30 | return $manifest;
31 | }
32 |
33 | public function create(Repository $repository)
34 | {
35 | return new Manifest($repository);
36 | }
37 |
38 | public function save(Manifest $manifest)
39 | {
40 | $this->_em->persist($manifest);
41 | $this->_em->flush();
42 | }
43 |
44 | public function incrementPulls(Manifest $manifest)
45 | {
46 | // Increment Manifest
47 | $this->_em
48 | ->createQuery(<<setParameter('manifest', $manifest->getId())
55 | ->execute()
56 | ;
57 |
58 | // Increment Repository
59 | $this->_em
60 | ->createQuery(<<setParameter('repository', $manifest->getRepository()->getId())
67 | ->execute()
68 | ;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/AppBundle/Entity/RepositoryRepository.php:
--------------------------------------------------------------------------------
1 | createQueryBuilder('r')
13 | ->where('r.owner = :account')
14 | ->setParameter('account', $account)
15 | ;
16 |
17 | if (!$isOwner) {
18 | $qb->andWhere('r.private = false');
19 | }
20 |
21 | return $qb;
22 | }
23 |
24 | public function findByNameOrCreate($name, User $owner)
25 | {
26 | $repository = $this->findOneByName($name);
27 | if (null === $repository) {
28 | try {
29 | $repository = $this->create($name, $owner);
30 | $this->save($repository);
31 | } catch (UniqueConstraintViolationException $e) {
32 | $repository = $this->findOneByName($name);
33 | }
34 | }
35 |
36 | return $repository;
37 | }
38 |
39 | public function create($name, User $owner)
40 | {
41 | return new Repository($name, $owner);
42 | }
43 |
44 | public function save(Repository $repository)
45 | {
46 | $this->_em->persist($repository);
47 | $this->_em->flush();
48 | }
49 |
50 | public function getLatestPublic($limit = 10)
51 | {
52 | return $this->findBy(['private' => false], null, $limit);
53 | }
54 |
55 | public function getMostStared($limit = 10)
56 | {
57 | return $this->findBy(['private' => false], ['stars' => 'desc'], $limit);
58 | }
59 |
60 | public function getMostPulled($limit = 10)
61 | {
62 | return $this->findBy(['private' => false], ['pulls' => 'desc'], $limit);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/AppBundle/Entity/RepositoryStar.php:
--------------------------------------------------------------------------------
1 | setRepository($repository)
44 | ->setUser($user);
45 | }
46 |
47 | /**
48 | * @return User
49 | */
50 | public function getUser()
51 | {
52 | return $this->user;
53 | }
54 |
55 | /**
56 | * @param User $user
57 | *
58 | * @return $this
59 | */
60 | public function setUser(User $user)
61 | {
62 | $this->user = $user;
63 |
64 | return $this;
65 | }
66 |
67 | /**
68 | * @return Repository
69 | */
70 | public function getRepository()
71 | {
72 | return $this->repository;
73 | }
74 |
75 | /**
76 | * @param Repository $repository
77 | *
78 | * @return $this
79 | */
80 | public function setRepository(Repository $repository)
81 | {
82 | $this->repository = $repository;
83 |
84 | return $this;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/AppBundle/Entity/RepositoryStarListener.php:
--------------------------------------------------------------------------------
1 | elasticaObjectPersister = $elasticaObjectPersister;
18 | }
19 |
20 | /**
21 | * @ORM\PostPersist
22 | */
23 | public function incStarsCount(RepositoryStar $repositoryStar, LifecycleEventArgs $events)
24 | {
25 | $this->updateStarsCount($repositoryStar, $events->getEntityManager(), '+');
26 | }
27 |
28 | /**
29 | * @ORM\PostRemove
30 | */
31 | public function decStarsCount(RepositoryStar $repositoryStar, LifecycleEventArgs $events)
32 | {
33 | $this->updateStarsCount($repositoryStar, $events->getEntityManager(), '-');
34 | }
35 |
36 | protected function updateStarsCount(RepositoryStar $repositoryStar, EntityManager $em, $operator)
37 | {
38 | $repository = $repositoryStar->getRepository();
39 |
40 | $qb = $em->createQueryBuilder()
41 | ->update(Repository::class, 'r')
42 | ->set('r.stars', "r.stars $operator 1")
43 | ->where('r.id = :id')
44 | ->setParameter('id', $repository->getId())
45 | ->getQuery();
46 | $qb->execute();
47 |
48 | $em->refresh($repository);
49 |
50 | $this->elasticaObjectPersister->replaceOne($repository);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/AppBundle/Entity/RepositoryStarRepository.php:
--------------------------------------------------------------------------------
1 | findOneBy([
12 | 'repository' => $repository,
13 | 'user' => $user,
14 | ]);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/AppBundle/Entity/User.php:
--------------------------------------------------------------------------------
1 | repository = $repository;
71 | }
72 |
73 | /**
74 | * Get id.
75 | *
76 | * @return int
77 | */
78 | public function getId()
79 | {
80 | return $this->id;
81 | }
82 |
83 | /**
84 | * @return Repository
85 | */
86 | public function getRepository()
87 | {
88 | return $this->repository;
89 | }
90 |
91 | /**
92 | * @param Repository $repository
93 | *
94 | * @return Manifest
95 | */
96 | public function setRepository($repository)
97 | {
98 | $this->repository = $repository;
99 |
100 | return $this;
101 | }
102 |
103 | /**
104 | * Set name.
105 | *
106 | * @param string $name
107 | *
108 | * @return Webhook
109 | */
110 | public function setName($name)
111 | {
112 | $this->name = $name;
113 |
114 | return $this;
115 | }
116 |
117 | /**
118 | * Get name.
119 | *
120 | * @return string
121 | */
122 | public function getName()
123 | {
124 | return $this->name;
125 | }
126 |
127 | /**
128 | * Set url.
129 | *
130 | * @param string $url
131 | *
132 | * @return Webhook
133 | */
134 | public function setUrl($url)
135 | {
136 | $this->url = $url;
137 |
138 | return $this;
139 | }
140 |
141 | /**
142 | * Get url.
143 | *
144 | * @return string
145 | */
146 | public function getUrl()
147 | {
148 | return $this->url;
149 | }
150 |
151 | /**
152 | * @return \DateTime
153 | */
154 | public function getLastCall()
155 | {
156 | return $this->lastCall;
157 | }
158 |
159 | /**
160 | * @param \DateTime $lastCall
161 | *
162 | * @return $this
163 | */
164 | public function setLastCall(\DateTime $lastCall)
165 | {
166 | $this->lastCall = $lastCall;
167 |
168 | return $this;
169 | }
170 |
171 | /**
172 | * @return string
173 | */
174 | public function getLastStatus()
175 | {
176 | return $this->lastStatus;
177 | }
178 |
179 | /**
180 | * @param string $lastStatus
181 | *
182 | * @return $this
183 | */
184 | public function setLastStatus($lastStatus)
185 | {
186 | $this->lastStatus = $lastStatus;
187 |
188 | return $this;
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/src/AppBundle/Event/DelayedEvent.php:
--------------------------------------------------------------------------------
1 | trigger = $trigger;
27 | $this->eventName = $eventName;
28 | $this->event = $event;
29 | }
30 |
31 | /**
32 | * @return string
33 | */
34 | public function getTrigger()
35 | {
36 | return $this->trigger;
37 | }
38 |
39 | /**
40 | * @return mixed
41 | */
42 | public function getEventName()
43 | {
44 | return $this->eventName;
45 | }
46 |
47 | /**
48 | * @return Event
49 | */
50 | public function getEvent()
51 | {
52 | return $this->event;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/AppBundle/Event/ManifestEvent.php:
--------------------------------------------------------------------------------
1 | manifest = $manifest;
18 | }
19 |
20 | public function getManifest()
21 | {
22 | return $this->manifest;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/AppBundle/EventListener/DelayedEventListener.php:
--------------------------------------------------------------------------------
1 | ['onDelayedEvent'],
18 | ];
19 | }
20 |
21 | public function onDelayedEvent(DelayedEvent $event, $eventName, EventDispatcherInterface $dispatcher)
22 | {
23 | if (!isset($this->events[$event->getTrigger()])) {
24 | $this->events[$event->getTrigger()] = [];
25 | $dispatcher->addListener($event->getTrigger(), [$this, 'triggerEvent']);
26 | }
27 |
28 | $this->events[$event->getTrigger()][] = $event;
29 | }
30 |
31 | public function triggerEvent(Event $triggerEvent, $eventName, EventDispatcherInterface $dispatcher)
32 | {
33 | /** @var DelayedEvent $delayedEvent */
34 | while ($delayedEvent = array_shift($this->events[$eventName])) {
35 | $dispatcher->dispatch($delayedEvent->getEventName(), $delayedEvent->getEvent());
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/AppBundle/EventListener/HeaderResponseListener.php:
--------------------------------------------------------------------------------
1 | ['onKernelResponse'],
14 | ];
15 | }
16 |
17 | public function onKernelResponse(FilterResponseEvent $event)
18 | {
19 | $event->getResponse()->headers->add([
20 | 'Docker-Distribution-Api-Version' => 'registry/2.0',
21 | ]);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/AppBundle/EventListener/ManifestPullListener.php:
--------------------------------------------------------------------------------
1 | ['onManifestPull'],
20 | ];
21 | }
22 |
23 | public function __construct(ObjectManager $om)
24 | {
25 | $this->om = $om;
26 | }
27 |
28 | public function onManifestPull(ManifestEvent $event)
29 | {
30 | $this->om->getRepository('AppBundle:Manifest')->incrementPulls($event->getManifest());
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/AppBundle/EventListener/ManifestPushListener.php:
--------------------------------------------------------------------------------
1 | ['onManifestPush'],
33 | ];
34 | }
35 |
36 | public function __construct(ObjectManager $om, Publisher $publisher, SerializerInterface $serializer)
37 | {
38 | $this->om = $om;
39 | $this->publisher = $publisher;
40 | $this->serializer = $serializer;
41 | }
42 |
43 | public function onManifestPush(ManifestEvent $event)
44 | {
45 | $manifest = $event->getManifest();
46 |
47 | $message = new Message($this->serializer->serialize($manifest, 'json', ['groups' => ['manifest_push']]));
48 | $this->publisher->publish('manifest_push', $message);
49 |
50 | $manifest->setUpdatedAt(new \DateTime());
51 | $this->om->flush();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/AppBundle/EventListener/RegistryExceptionListener.php:
--------------------------------------------------------------------------------
1 | ['onKernelException'],
17 | ];
18 | }
19 |
20 | public function onKernelException(GetResponseForExceptionEvent $event)
21 | {
22 | $path = $event->getRequest()->getPathInfo();
23 | if ($path !== '/token' && 0 !== strpos($path, '/v2/')) {
24 | return;
25 | }
26 |
27 | $exception = $event->getException();
28 | $code = $exception instanceof HttpException ? $exception->getStatusCode() : $exception->getCode();
29 | $headers = $exception instanceof HttpException ? $exception->getHeaders() : [];
30 | $errorCode = isset(Response::$statusTexts[$code]) ? Response::$statusTexts[$code] : 'error';
31 |
32 | $response = new JsonResponse([
33 | 'errors' => [
34 | [
35 | 'code' => strtoupper($errorCode),
36 | 'details' => null,
37 | 'message' => $exception->getMessage(),
38 | ],
39 | ],
40 | ], $code, $headers);
41 |
42 | $event->setResponse($response);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/AppBundle/Form/Type/RepositoryType.php:
--------------------------------------------------------------------------------
1 | add('title')
20 | ->add('description')
21 | ->add('private', CheckboxType::class, ['required' => false])
22 | ;
23 | }
24 |
25 | /**
26 | * @param OptionsResolver $resolver
27 | */
28 | public function configureOptions(OptionsResolver $resolver)
29 | {
30 | $resolver->setDefaults(array(
31 | 'data_class' => 'AppBundle\Entity\Repository',
32 | ));
33 | }
34 |
35 | /**
36 | * @return string
37 | */
38 | public function getBlockPrefix()
39 | {
40 | return 'repository';
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/AppBundle/Form/Type/WebhookType.php:
--------------------------------------------------------------------------------
1 | add('name')
19 | ->add('url')
20 | ;
21 | }
22 |
23 | /**
24 | * @param OptionsResolver $resolver
25 | */
26 | public function configureOptions(OptionsResolver $resolver)
27 | {
28 | $resolver->setDefaults(array(
29 | 'data_class' => 'AppBundle\Entity\Webhook',
30 | ));
31 | }
32 |
33 | /**
34 | * @return string
35 | */
36 | public function getBlockPrefix()
37 | {
38 | return 'webhook';
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/AppBundle/Manager/LayerManager.php:
--------------------------------------------------------------------------------
1 | om = $om;
24 | $this->fs = $fs;
25 | }
26 |
27 | public function save(Layer $layer)
28 | {
29 | $this->om->persist($layer);
30 | $this->om->flush();
31 | }
32 |
33 | public function create()
34 | {
35 | return new Layer();
36 | }
37 |
38 | public function write(Layer $layer, $content)
39 | {
40 | $path = $this->getContentPath($layer);
41 |
42 | # Append content manually as Flysystem does not support it
43 | if ($this->fs->has($path)) {
44 | $stream = fopen('php://temp', 'w');
45 | stream_copy_to_stream($this->fs->readStream($path), $stream);
46 | stream_copy_to_stream($content, $stream);
47 | } else {
48 | $stream = $content;
49 | }
50 |
51 | $this->fs->putStream($path, $stream);
52 | }
53 |
54 | public function read(Layer $layer)
55 | {
56 | return $this->fs->readStream($this->getContentPath($layer));
57 | }
58 |
59 | public function getSize(Layer $layer)
60 | {
61 | return $this->fs->getSize($this->getContentPath($layer));
62 | }
63 |
64 | public function computeDigest(Layer $layer)
65 | {
66 | $hc = hash_init('sha256');
67 | hash_update_stream($hc, $this->fs->readStream($this->getContentPath($layer)));
68 |
69 | return 'sha256:'.hash_final($hc);
70 | }
71 |
72 | protected function getContentPath(Layer $layer)
73 | {
74 | return 'layers/'.$layer->getUuid();
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/AppBundle/Manager/RepositoryStarManager.php:
--------------------------------------------------------------------------------
1 | om = $om;
18 | }
19 |
20 | protected function getRepository()
21 | {
22 | return $this->om->getRepository('AppBundle:RepositoryStar');
23 | }
24 |
25 | public function isStarredByUser(Repository $repository, User $user)
26 | {
27 | $repositoryStar = $this->getRepository()->findOneByRepositoryAndUser($repository, $user);
28 |
29 | return null !== $repositoryStar;
30 | }
31 |
32 | public function star(Repository $repository, User $user)
33 | {
34 | $repositoryStar = RepositoryStar::create($repository, $user);
35 |
36 | $this->om->persist($repositoryStar);
37 | $this->om->flush();
38 | }
39 |
40 | public function unstar(Repository $repository, User $user)
41 | {
42 | $repositoryStar = $this->getRepository()->findOneByRepositoryAndUser($repository, $user);
43 |
44 | $this->om->remove($repositoryStar);
45 | $this->om->flush();
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/AppBundle/Manager/SearchManager.php:
--------------------------------------------------------------------------------
1 | pagerFactory = $pagerFactory;
35 | $this->repositoryType = $repositoryType;
36 | $this->tokenStorage = $tokenStorage;
37 | }
38 |
39 | /**
40 | * @param string $keyword
41 | *
42 | * @return HttpPager
43 | */
44 | public function createPager($keyword)
45 | {
46 | $fields = [
47 | 'name' => new Field('name'),
48 | 'title' => new Field('title'),
49 | 'description' => new Field('description'),
50 | 'private' => new Field('private'),
51 | ];
52 | $adapter = new ElasticaAdapter($this->repositoryType, $fields, $this->createSearchQuery($keyword));
53 |
54 | return $this->pagerFactory->createHttpPager($adapter);
55 | }
56 |
57 | /**
58 | * @param string $keyword
59 | *
60 | * @return Query
61 | */
62 | protected function createSearchQuery($keyword)
63 | {
64 | return Query::create((new BoolQuery())
65 | ->addMust($this->getTermQuery($keyword))
66 | ->addMust($this->getPrivacyQuery())
67 | );
68 | }
69 |
70 | /**
71 | * @param string $keyword
72 | *
73 | * @return SimpleQueryString
74 | */
75 | protected function getTermQuery($keyword)
76 | {
77 | return new SimpleQueryString($keyword, ['name', 'title', 'description']);
78 | }
79 |
80 | /**
81 | * @return BoolQuery
82 | */
83 | protected function getPrivacyQuery()
84 | {
85 | $query = new BoolQuery();
86 | $query->addShould(new Term(['private' => false]));
87 | if (($user = $this->tokenStorage->getToken()->getUser()) instanceof User) {
88 | $query->addShould(new Term(['owner.id' => $user->getId()]));
89 | }
90 |
91 | return $query;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/AppBundle/Security/RegistryEntryPoint.php:
--------------------------------------------------------------------------------
1 | urlGenerator = $urlGenerator;
21 | }
22 |
23 | public function start(Request $request, AuthenticationException $authException = null)
24 | {
25 | $tokenEndpoint = $this->urlGenerator->generate('registry_token', [], UrlGeneratorInterface::ABSOLUTE_URL);
26 | $serviceEndpoint = $this->urlGenerator->generate('index', [], UrlGeneratorInterface::ABSOLUTE_URL);
27 |
28 | $data = [
29 | 'errors' => [
30 | [
31 | 'code' => 'UNAUTHORIZED',
32 | 'details' => null,
33 | 'message' => 'access to the requested resource is not authorized',
34 | ],
35 | ],
36 | ];
37 |
38 | return new JsonResponse($data, 401, [
39 | 'WWW-Authenticate' => sprintf('Bearer realm="%s",service="%s"', $tokenEndpoint, $serviceEndpoint),
40 | ]);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/AppBundle/Security/Voter/RepositoryVoter.php:
--------------------------------------------------------------------------------
1 | om = $om;
36 | $this->roleHierarchyVoter = $roleHierarchyVoter;
37 | $this->logger = $logger;
38 | }
39 |
40 | public function supportsAttribute($attribute)
41 | {
42 | return in_array($attribute, [self::READ, self::WRITE]);
43 | }
44 |
45 | public function supportsClass($class)
46 | {
47 | return true;
48 | }
49 |
50 | public function vote(TokenInterface $token, $repository, array $attributes)
51 | {
52 | // abstain vote by default in case none of the attributes are supported
53 | $vote = self::ACCESS_ABSTAIN;
54 |
55 | foreach ($attributes as $attribute) {
56 | if (!$this->supportsAttribute($attribute)) {
57 | continue;
58 | }
59 |
60 | // as soon as at least one attribute is supported, default is to deny access
61 | $vote = self::ACCESS_DENIED;
62 |
63 | if ($this->isGranted($attribute, $repository, $token)) {
64 | // grant access as soon as at least one voter returns a positive response
65 | return self::ACCESS_GRANTED;
66 | }
67 | }
68 |
69 | return $vote;
70 | }
71 |
72 | protected function isGranted($attribute, $repository, TokenInterface $token)
73 | {
74 | // Admin can do everything
75 | if (VoterInterface::ACCESS_GRANTED === $this->roleHierarchyVoter->vote($token, null, ['ROLE_ADMIN'])) {
76 | return true;
77 | }
78 |
79 | $user = $token->getUser();
80 |
81 | // We allow to check by repository name
82 | // Needed when pushing the first manifest, that will create the repository
83 | if (!$repository instanceof Repository) {
84 | $name = $repository;
85 | $repository = $this->om->getRepository('AppBundle:Repository')->findOneByName($repository);
86 | if (null === $repository) {
87 | // repository does not exist
88 | // User tries to access root namespace but is not ADMIN
89 | if (false === strpos($name, '/')) {
90 | return false;
91 | }
92 |
93 | // Use not logged
94 | if (!$user instanceof UserInterface) {
95 | return false;
96 | }
97 |
98 | list($tld) = explode('/', $name);
99 |
100 | return $tld === $user->getUsername();
101 | }
102 | }
103 |
104 | $isOwner = $user instanceof UserInterface && $repository->getOwner() === $user;
105 |
106 | switch ($attribute) {
107 | case self::READ:
108 | return $isOwner || $repository->isPublic();
109 |
110 | case self::WRITE:
111 | return $isOwner;
112 | }
113 |
114 | return false;
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/AppBundle/Twig/AppExtension.php:
--------------------------------------------------------------------------------
1 | repositoryStarManager = $repositoryStarManager;
17 | }
18 |
19 | public function getFunctions()
20 | {
21 | return [
22 | new \Twig_SimpleFunction('is_starred_by_user', [$this, 'isStarredByUser']),
23 | ];
24 | }
25 |
26 | public function isStarredByUser(Repository $repository, User $user)
27 | {
28 | return $this->repositoryStarManager->isStarredByUser($repository, $user);
29 | }
30 |
31 | public function getName()
32 | {
33 | return 'app_extension';
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/var/cache/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamluc/dunkerque/36662ef4d28dd61bb5cb03748861d46c5329438e/var/cache/.gitkeep
--------------------------------------------------------------------------------
/var/jwt/private.pem.dist:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | Proc-Type: 4,ENCRYPTED
3 | DEK-Info: AES-256-CBC,882B7F77130F88B7B9DE02C484EBC88E
4 |
5 | 8Xj8pXbsAo9N1IlnzPptUvQOqudFcOnWbEM7zWFKyNzngTexFVPjNZAncR1gLolF
6 | 9o15pzQSQux6KeBWLPf627/7mo+UAXQnj3vOtjONsKN3/v3wvzC7EUSzKBiOT3bb
7 | N4nVrQT8CuPk4LX5mglXupW1TPEujsHiFWusqiwdyMOySt9pudFJHlXNU5XBFIGr
8 | diiToYnxbisBBlKfa2LCx0deIJPCeOy/ggJ39g2A6dYeo/w1RLuMWktUyKsmJr02
9 | FksZ2sRbTLoYxf7uDTvHZ4xuRju8UredZmXzJlfk3gTq1zSmViuzUn9De1nvYNP3
10 | 9FtxCQCrBCBqcu+qSwYYuxm1tTbq/Ad5tKmq3S1M1alotauLrnzd8/Wq9rt5folX
11 | qOHun2NTktuOjeGsdDFprUNFzC7F75nxqbouQpvPtIwxVYUQr5AcQzyyVIyL4h2K
12 | Lqpvqc8AJpNdkcH8fcqtZsd12PstYqqVcd2QuR2Zd9dX+1odfax1pl/Ui18ytB11
13 | Z82OqnxZcDQH13ZXK7zuifarrVwAwBXgye6bjG09coGjOjpWL1wDNQxgEOEQIeSy
14 | Xy5sTK3YnKJYLhORZfcltN8fkfIrjD4HiZGUuCrZ7Yrh+aJn66ioAGZ1O0NjB09v
15 | 3WU+mkNFxpnCaaKz4YWXfNzEpV2QiVELpGmwvPX0gvhnjAnPIMtWrBG4GDjzHsCn
16 | Vz7FNZk1jjMC4/WAbhw95OLtg8DIjrz6/Dw1UPGxDrHC7MoSZjx063xcdF1wl8Lh
17 | SP3Tdrq0dX+CweOdXIM+9bqrsnjPMj/6479Kwb1+Dizzat2pXZb1MOqbiVXNa1sv
18 | VcbMCwz3sNPuMoiB1a+/5pjqnisCWTSHZHzqIz69kRFdSED+nHceUfn5Egeuii3g
19 | 7OB3l1dMrYTJ2jyTWRoNkMY+0C1ec6W+/FtfElX+2//72v15iRMvd1stFNa9TYYt
20 | ODu5ZYBVjz0wMBpw//4NFzBCVq1PUL6e91oQY/W0jQKUBxoDqbJ/s/0/+y7Czewf
21 | zVSoCER9NPYfVqrY0yuZRk5nolGdZKQOyWELQR/1qHlcvzE5tL7FIV+hYQJomR7f
22 | XSEXtWS5YhrVhZ4TLhNj4T6YbKdJnVObS9XWs4fuBfwOlqCFgDYrrJBGZMmEMrWi
23 | NedBE4e+nO4Lf9dtGPN2KhEaKqhqSuyw97DEVdKhx2C39S7EYcZD8TA6Xxsd9UVa
24 | ObJaEab9JyGcm5CsFa4ebxRIg+xXyLP8arHSYH77bG3TFQ3txA3LXIMM9BmEypkZ
25 | GASISS0/vwfFaydYihZjVk8LigLcoGgxHLaQM6rXGTV5xTgVJ/jTYwgEDOkoto5O
26 | 0701vPiUjRiHnVlvYMmy5lDPvtvvrwnH/EDDd3L5Ibly9KOC581niG2/0aUShBih
27 | rK1qXucTQbIkpsU97/ms582gcLr3WgkG+znYjvPRWzPMxiLTzlTkt1KLGHmH4TM4
28 | xIV2kXqnffTm9izpzlt+ZkmGRy+FLlbygfQfKofqj5T0BUT66on9KU0B7GLxkIrh
29 | ffbXTgvm2m2U/2txQSDZxPrz0+7qP8WyS1nlvYhi5vV2YRCjbKr7XfMxGgbXAwfl
30 | oAdtQNMGXKkQaY0JxRRyq6UQvARz3e08Q0x/nAI1VHGBajPjAyq2z+f1/OlZ7Z+w
31 | HX5HqCfHNcgGAlgprzAt6ws1X+N8pGKz3GZpp43T5aySagIaT5rGvLLtVsf0YxL0
32 | l29LxJ7c5+fHQZg5WguJQhBhStCboZZXYBCcACz3G9DelnNyQIlb98LT2+f0H+BO
33 | HW/3ua0KmWpBiGMICeAG1h8wmQ0E3xAjxsLLpjVDM0O0bDBhkxF/NQ9o/bcGBzZ3
34 | rnbHTLdAsX3Jxd/lNN5Rm9Io5jcRbgeOO64gKfUi8DjkuzuBwwUIScrazMzPnLxt
35 | 4npMDcDfaijy/KaZZm0GyxDBP37ms2wrPVNfBJoLlXilRKOaZPgfAJZePsG5bAjj
36 | CwZrW4B5XoP3G0f6IFWibRPrlA8PoU8A1LIwCML0CDVcoG8tj5gsnQ4d+WSjdt0i
37 | wODSIcpn3BwyPDqwXajbgLQN4y16svyedmOuQ3HUHjikIfHXJs83cCHBx7waBrt4
38 | snVNF9ob4Xme56xD67QyA45Hs/IpibEBlz4b5MHI0kNCMS7P1vpk2bX9dm2NXHXN
39 | 5IGjzbcn1mXMhS/yI6DpoJlszh78BoBEjm1q1z2EpmfC1hl/LbxrOiBHdM81Y+bs
40 | cXU9oT9wTt3PvynAijKzCsxJIS7UWnx8wNTGLg/1CHFxnRe6EDiOr/lbG/+CFKYS
41 | zWJ5d8WmeJW12fvYBoxS7apzdyiwa2J6zb47n1FfKZEl62ulxSF/XNTXJeOD6n5D
42 | J9+KZbL54Ts/4nnUF1WGjaZAsjzCMxZy60iHQ/E3xt4kcIdMP91KrC8/iPBKi8qU
43 | /Bij17d6kMo0+cGELjDZ7Ju2VMUKstD4es+iW3TJ/ptqX6IE8+l5aHc+RNqpwnFB
44 | bYunEdS8be0ff4FvlQ7bYWtH4SkZzhvqZ2CWj9LfDX2htXTvIMyEpbYiYUbG98ku
45 | TwqgQ6QW+9PuSeC7RIc+8tMRiEO5QlF+ybVX8l5uDnF1fsXru9rCmMwJ/1G5+s7T
46 | xxec2GANFfVN9RHccluYnnCmucOssiimhZ/TEDfqDkMjr+kU1/96g7oWRYCx3CG2
47 | iIpxUIACLJNPw6FIvu1vtzm8cfNTwYw6gslRhT3WLfFbg7VkRoM6g8nhCaxj6B61
48 | 3TfGoOHoLMK4MY/yJMmOIZbn1KbKrl/HjUg+jrfPOBwhkoLvOTw4pqC3xNRROSqM
49 | HuoiqjYCN5oibn1SAx/UfFIAoskTJGJL7GwOVCJ9V9bG3Tcnbj2yga6Mhu3YOX+m
50 | cZnW7ByjnL+/nAInanqXcwqjPvrcI1km1Qloy13zFOCh2Z62yXiS0KTqvV4REV77
51 | V6tuI4yaMlSOkroZGQeFKXnyKdceFIBZ8+uBrkBdsOoj3orhgi4bAdlsMjOV+Qdp
52 | v2Y4NmR9SOJ2jOUI8Lt0S8sf7wDfsC2JxCavMQHn+y9jh1CREP0oxGSRlq3t92p1
53 | cNiC9RmcFmWqY4njDPylmbEmHkwRrxMIxenrWlufC5XP5L2/sjs4s0hRy5CDQMVv
54 | -----END RSA PRIVATE KEY-----
55 |
--------------------------------------------------------------------------------
/var/jwt/public.pem.dist:
--------------------------------------------------------------------------------
1 | -----BEGIN PUBLIC KEY-----
2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwxghthNY28yQuPMXBDy+
3 | 3hc803pN852HlBEFHoKN8roRN5HewGkw3MgwnXBIRXAblSmdH8xUQjST9ngGUe+o
4 | vlA1atc9cFYC1siISEbrlrb9srupnzCh5WKIOaoWyAT1fY0MjZgLKCF8Mfcgbskk
5 | 0Is8IHy2wKAQ1BuAbTO7AHDAxTd53+rXByDR0OqpcEhHZ3uAyVu+hT+OFq2SsNKJ
6 | LHsnFLj3zLntwHNOICLUk3l2qraoWZ/T76+HpPZe5YDGgnUdHuaCAUITjCd8VqHd
7 | 0eQKg7D7qpjEI9iGCNWxqVbIQ0t9u6qHqCsLEQlTRYaLyLw6DKfkTWgCNeAY3MDz
8 | VIVJ5DYcZYLhENJQRGwa8v0y/aMkrDfoxeHdcZLSgYprRux2YuttN2L/qqk88XOh
9 | IWp9Ft30+r9ZTDKzH/Tf6llwVHjKfFAFEhYr0I5f05297yldfXpFSTaT1nQZ2pR8
10 | DIUWZ1llHiiKGK3uymoPUqW99OV63FlUfoOYDUihZwheQm7PVUkreKd5n4kQTmEw
11 | Xz16ahk3xWqReM5JgSO6MGpItez5cxagW7WIWvGhAUPrb8n3yp6O/ztJcDwyn1ne
12 | 1zllp2gbircBPoSfzPPAlanwLZgEHpww79F3/q5646kBDvAQYCnQ5z5sLPkN7jb5
13 | IrumAJbGufbw7qiVQF0p3xsCAwEAAQ==
14 | -----END PUBLIC KEY-----
15 |
--------------------------------------------------------------------------------
/var/logs/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamluc/dunkerque/36662ef4d28dd61bb5cb03748861d46c5329438e/var/logs/.gitkeep
--------------------------------------------------------------------------------
/var/storage/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamluc/dunkerque/36662ef4d28dd61bb5cb03748861d46c5329438e/var/storage/.gitkeep
--------------------------------------------------------------------------------
/web/.htaccess:
--------------------------------------------------------------------------------
1 | # Use the front controller as index file. It serves as a fallback solution when
2 | # every other rewrite/redirect fails (e.g. in an aliased environment without
3 | # mod_rewrite). Additionally, this reduces the matching process for the
4 | # start page (path "/") because otherwise Apache will apply the rewriting rules
5 | # to each configured DirectoryIndex file (e.g. index.php, index.html, index.pl).
6 | DirectoryIndex app.php
7 |
8 | # By default, Apache does not evaluate symbolic links if you did not enable this
9 | # feature in your server configuration. Uncomment the following line if you
10 | # install assets as symlinks or if you experience problems related to symlinks
11 | # when compiling LESS/Sass/CoffeScript assets.
12 | # Options FollowSymlinks
13 |
14 | # Disabling MultiViews prevents unwanted negotiation, e.g. "/app" should not resolve
15 | # to the front controller "/app.php" but be rewritten to "/app.php/app".
16 |
17 | Options -MultiViews
18 |
19 |
20 |
21 | RewriteEngine On
22 |
23 | # Determine the RewriteBase automatically and set it as environment variable.
24 | # If you are using Apache aliases to do mass virtual hosting or installed the
25 | # project in a subdirectory, the base path will be prepended to allow proper
26 | # resolution of the app.php file and to redirect to the correct URI. It will
27 | # work in environments without path prefix as well, providing a safe, one-size
28 | # fits all solution. But as you do not need it in this case, you can comment
29 | # the following 2 lines to eliminate the overhead.
30 | RewriteCond %{REQUEST_URI}::$1 ^(/.+)/(.*)::\2$
31 | RewriteRule ^(.*) - [E=BASE:%1]
32 |
33 | # Sets the HTTP_AUTHORIZATION header removed by apache
34 | RewriteCond %{HTTP:Authorization} .
35 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
36 |
37 | # Redirect to URI without front controller to prevent duplicate content
38 | # (with and without `/app.php`). Only do this redirect on the initial
39 | # rewrite by Apache and not on subsequent cycles. Otherwise we would get an
40 | # endless redirect loop (request -> rewrite to front controller ->
41 | # redirect -> request -> ...).
42 | # So in case you get a "too many redirects" error or you always get redirected
43 | # to the start page because your Apache does not expose the REDIRECT_STATUS
44 | # environment variable, you have 2 choices:
45 | # - disable this feature by commenting the following 2 lines or
46 | # - use Apache >= 2.3.9 and replace all L flags by END flags and remove the
47 | # following RewriteCond (best solution)
48 | RewriteCond %{ENV:REDIRECT_STATUS} ^$
49 | RewriteRule ^app\.php(/(.*)|$) %{ENV:BASE}/$2 [R=301,L]
50 |
51 | # If the requested filename exists, simply serve it.
52 | # We only want to let Apache serve files and not directories.
53 | RewriteCond %{REQUEST_FILENAME} -f
54 | RewriteRule .? - [L]
55 |
56 | # Rewrite all other queries to the front controller.
57 | RewriteRule .? %{ENV:BASE}/app.php [L]
58 |
59 |
60 |
61 |
62 | # When mod_rewrite is not available, we instruct a temporary redirect of
63 | # the start page to the front controller explicitly so that the website
64 | # and the generated links can still be used.
65 | RedirectMatch 302 ^/$ /app.php/
66 | # RedirectTemp cannot be used instead
67 |
68 |
69 |
--------------------------------------------------------------------------------
/web/app.php:
--------------------------------------------------------------------------------
1 | unregister();
18 | $apcLoader->register(true);
19 | */
20 |
21 | $kernel = new AppKernel('prod', false);
22 | $kernel->loadClassCache();
23 | //$kernel = new AppCache($kernel);
24 |
25 | // When using the HttpCache, you need to call the method in your front controller instead of relying on the configuration parameter
26 | //Request::enableHttpMethodParameterOverride();
27 | $request = Request::createFromGlobals();
28 | $response = $kernel->handle($request);
29 | $response->send();
30 | $kernel->terminate($request, $response);
31 |
--------------------------------------------------------------------------------
/web/app_dev.php:
--------------------------------------------------------------------------------
1 | loadClassCache();
30 | $request = Request::createFromGlobals();
31 | $response = $kernel->handle($request);
32 | $response->send();
33 | $kernel->terminate($request, $response);
34 |
--------------------------------------------------------------------------------
/web/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamluc/dunkerque/36662ef4d28dd61bb5cb03748861d46c5329438e/web/apple-touch-icon.png
--------------------------------------------------------------------------------
/web/assets/fonts/bootstrap/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamluc/dunkerque/36662ef4d28dd61bb5cb03748861d46c5329438e/web/assets/fonts/bootstrap/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/web/assets/fonts/bootstrap/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamluc/dunkerque/36662ef4d28dd61bb5cb03748861d46c5329438e/web/assets/fonts/bootstrap/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/web/assets/fonts/bootstrap/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamluc/dunkerque/36662ef4d28dd61bb5cb03748861d46c5329438e/web/assets/fonts/bootstrap/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/web/assets/fonts/bootstrap/glyphicons-halflings-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamluc/dunkerque/36662ef4d28dd61bb5cb03748861d46c5329438e/web/assets/fonts/bootstrap/glyphicons-halflings-regular.woff2
--------------------------------------------------------------------------------
/web/assets/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamluc/dunkerque/36662ef4d28dd61bb5cb03748861d46c5329438e/web/assets/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/web/assets/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamluc/dunkerque/36662ef4d28dd61bb5cb03748861d46c5329438e/web/assets/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/web/assets/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamluc/dunkerque/36662ef4d28dd61bb5cb03748861d46c5329438e/web/assets/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/web/assets/fonts/glyphicons-halflings-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamluc/dunkerque/36662ef4d28dd61bb5cb03748861d46c5329438e/web/assets/fonts/glyphicons-halflings-regular.woff2
--------------------------------------------------------------------------------
/web/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamluc/dunkerque/36662ef4d28dd61bb5cb03748861d46c5329438e/web/favicon.ico
--------------------------------------------------------------------------------
/web/robots.txt:
--------------------------------------------------------------------------------
1 | # www.robotstxt.org/
2 | # www.google.com/support/webmasters/bin/answer.py?hl=en&answer=156449
3 |
4 | User-agent: *
5 | Disallow:
6 |
--------------------------------------------------------------------------------